Overview - Save/Gets

We observed earlier that XyWrite could edit up to 9 files at one time. One might think, therefore, that an "editing context" at any given time would consist of at most 9 files, and not much more.

This is not the case, however. XyWrite also makes provisions for the temporary retention (while editing) of up to about 1000 "Save/Gets," (or S/Gs) each of which can hold a string of from 0 to several thousand characters. S/Gs can also hold decimal numbers of (apparently) arbitrary precision (including a portion "to the right of the decimal point"), which are encoded in a some way, and which are used when you ask XyWrite to do certain kinds of arithmetic operations. These can be easily converted to a normalized string representation, and a valid string representation can be easily converted a XyWrite "number." As with files, S/G strings can contain normal characters, 3 byte alternately encoded characters, and 3 byte encoded primitives. Since they can contain any characters, S/Gs also can of course contain XyWrite guillemet bracketed commands.

A number of the primitive codes that can be assigned to keys via the keyboard file have to do with saving data in, and retrieving data from, S/Gs. The "@A" through "@Z" and "@0" through "@9" primitive codes, when executed via a keystroke or an XPL program, copy data from one of the 36 S/Gs which are identified as S/Gs A-Z, and 0-9, respectively, into a file (at the cursor location). The "SV" keyboard function code prompts for a single additional keystroke (from A-Z or 0-9), and saves a marked (or "defined") area of text from a file into one of these 36 S/Gs. The "SD" keyboard primitive Shows a "Directory" of contents for any of these 36 S/Gs which is currently in use, showing the first few characters of each S/G value. The "SK" primitive prompts for a Key (A-Z, 0-9), and Shows the entire contents of the associated S/G.

So, for example, if you were writing a document that made frequent references to "The Constitution of the United State of America," you could copy that string into a S/G, and then save yourself a fair amount of typing by inserting that S/G into your document as required for each reference.

The 36 S/Gs identified as S/Gs A-Z and 0-9 occupy a special place in the XyWrite S/G catalog, because XyWrite provides functionality for these S/Gs which allows fairly easy impromptu access by a user at a keyboard. Another 36 S/Gs identified as S/Gs &A-&Z and &0-&9 also exist, which are somewhat less versatile (they can only hold "programs"), but which also have a set of 36 keyboard primitives defined on their behalf, to simplify access.

All of the S/Gs mentioned so far are "persistent." That is, any information placed in them remains there until/unless replaced by new information, until explicitly cleared (with a REMOVE, CLRSGT, or CLRASGT command), or until the end of the current XyWrite session. Contents of the first 36 S/Gs can also be saved in files, to be reinstated on subsequent XyWrite sessions.

In addition to the 72 S/Gs mentioned so far, there are 99 more, which are identified as "001" through "099". These can also be referred to as S/Gs 01 to 99. Note that S/G "01" is not the same as S/G "1", the latter being one of the "special 36" mentioned above. S/Gs 01 through 99 have the special property that their contents are retained only as long as some "XPL program" (discussed shortly) is being executed. For "XPL programmers," these provide for "temporary variable" storage for use by the program, wherein the storage used is automatically released for other usage as soon as the current XPL program finishes.

Then there are 900 more S/Gs, identified as 100 through 999. These are generally accessible only via XPL programs. Unlike 01 thought 99, these S/Gs are also persistent for the duration of a XyWrite session.

There is also another entitity which can perhaps be thought of as a S/G. XPL programs (described later) can be loaded into S/Gs, with a program being "executed" by specifying the S/G containing that XPL program. But programs can also be executed from a file, using the "RUN" command, which accepts a filename argument. A RUN with no filename argument will execute the last program executed (in the current session) via the RUN command. In this case, the last executed program is executed from memory -- no access to disk occurs. One could think of this memory as a special "implicit" S/G accessible only via the RUN command. Moreover, if the last RUN program is large, the associated "implicit S/G" continues to use main memory (as with any other S/G contents), until it is explicitly replaced by RUNning a smaller program.

Lastly, there is S/G 000, which is also addressable as S/G 00. This is a S/G that is updated "automatically" by XyWrite under various conditions to reflect the contents of the command line. This S/G allows XPL programs to determine command line contents.

For the 36 S/Gs labeled A-Z and 0-9, the simple 1 letter naming scheme is probably okay, and the scheme does allow binding some of the S/G operations to the keyboard in ways that can be remembered. For S/Gs 001 through 999, the S/G naming scheme is extremely hard to manage, especially since these S/Gs are THE storage mechanism that one must use in lieu of symbolically named variables available in most languages. And the fact that these globally named S/Gs must also serve the role of what would be subroutine "local" variables in other languages only adds insult to injury.

Overview - streams and stream processing

As someone with some programming background, I find it easiest to understand XyWrite by thinking of it as a program that mostly processes "streams" of data. For example, when we enter (a stream of) data in a file, XyWrite processes the stream of data in the file to produce another stream that fills the display -- the latter stream having been modified to reflect margins, indentation, and such, as we expect from a "WYSIWYG" word processor. When the file is to be printed, a different stream is produced -- namely the stream that causes a printer to print the file according to our visual printing expectations.

But XyWrite processes streams in other contexts as well. For example, after a key is struck, the stream represented by the corresponding line of the keyboard file is processed as a stream, with "character" data typically being simply sent to the file (or the command line), and with all 2 character primitive codes causing invocation of the associated primitive functionality, as the "stream" from the keyboard entry is processed from left to right.

One of the stream conversions -- generating a printer stream from a file data stream, is individual printer specific, and is a process that is controlled by a large amount of data which must be loaded via a XyWrite "printer file" that corresponds to the specific printer that is being used. A "printer file" is a normal "text" file that can be created or edited by XyWrite, but which is constructed to contain data about all of the fonts, font variations (e.g., italics, bold), and font sizes available on the printer that the printer file represents, including character width information for each character in each available font/size/variation combination. The character width information allows XyWrite to calculate how many characters (of the current font/size/variation) will fit on each line, which it must do to layout a printed page correctly.

Generally, the transformations associate with generating a printer stream are reasonably well documented in the XyWrite manual and in Herbert Tyson's "XyWrite Revealed" book, and won't be discussed significantly in this note. For this transformation, though, the goal is pretty obvious -- make the page look "right."

The stream generated to fill the computer display screen (in expanded mode) from a file stream is also dependent on the printer file currently loaded. XyWrite generally does its best to represent on-screen how the printed page layout will appear. But since XyWrite is a DOS "console" application, XyWrite only has the normal DOS, fixed width, character mode, display-card-generated font to work with in drawing the screen display. Even so, the normal XyWrite display accurately displays the boundaries where a paragraph will "line-wrap" when the data is printed the current printer.

Since (almost invariably) more characters will fit on a printed line that will fit on the 80 character DOS console line, this very accurate representation of where line-wrapping will occur can actually be a nuisance -- much horizontal scrolling is required just to read what one has just written when the data screen is formatted this way. To alleviate this problem, probably most XyWrite users (myself included) maintain (and usually load) a minimal printer file which represents a fictitious printer. The parameters of this fictitious printer correspond to a printer where in all fonts, in all variations and sizes, print as fixed width fonts at 10 characters per inch. With such a printer, all of the characters that would fit on a (fictitious) printed (8.5" by 11", assuming at least 1/4" side margins) page line will also fit on a screen display line, thus allowing the document to be read on screen without the need for constant horizontal scrolling. Paragraph reflowing, and indentation constructs such as numbered or bulleted lists still display well, however, and this is generally what is (by far) most important during the writing phase of constructing a document.

The "fictitious" printer file that I have just described is, in fact, nearly identical to XyWrite's default behavior, if no printer files is loaded at all. However, actually having a printer file to load for this purpose remains a sensible strategy, because it serves as the (only available) mechanism to "unload" the printer file for a real printer, when one wants to do so. As you might surmise, since XyWrite's default behavior is already quite near to what we want for a "fictitious" printer file, the constructions of such a file is a pretty minimal activity.

The general idea of a special (ficticious) "printer" definition to be used at at document composition time, may in fact be a much more significant idea than meets the eye, and may partially account for why writers like XyWrite so much. In effect, this allows a writer to work more within the world of the relatively narrow newspaper column, rather than the world of much wider world of the 8.5" wide printed page. A quick web search for "optimum column width," for example, yielded the following comment:

"At normal reading distance the arc of the visual field is only a few inches - about the width of a well-designed column of text, or about 12 words per line. Research shows that reading slows and retention rates fall as line length begins to exceed the ideal width."
In summary -- printer files can be and are loaded to match a file to a specific printer and prepare for printing, but different printer files can be and are loaded during document composition, solely for purposes of controlling on screen formatting in ways that are best suited to document composition and review.

For many or most other word processors, this problem may be harder to solve. Construction of "printer files" (which can be done by a user in XyWrite) is replace by a host of "printer drivers" for various the printers, and those drivers for the most part cannot be constructed by users.

Note that there are other significant differences in this area between XyWrite and some other word processors, such as WordPerfect (5.1 for DOS). With XyWrite, selecting a printer (e.g., loading a printer file) has absolutely no effect on any document's source file contents -- it only effects the stream processes that reflect that file to either the screen or the printer, and hence only affects the ephemeral streams created for those purposes. In WordPerfect, changing the printer is reflected back into the source file, as the source file itself is "reformatted" for the selected printer (according to the WordPerfect documentation). (Among other things, WordPerfect has a notion of "soft returns," which are actually included in the source file, which reflect where line splitting occurred as a result of "paragraph reflowing" for "the current printer." There is no such notion in XyWrite.)

But, in addition to the printer and display stream transformations, there are many other stream transfers and transformations to consider in XyWrite, and our focus here is largely on the stream processes that pertain to XyWrite "programming." XyWrite includes both a "keyboard macro" interpreter (exemplified by the «PVxx» embedded command) and a programming language interpreter, for a language which XyQuest calls "XPL". The definition and implementation of both of these interpreters was, I believe, greatly influenced by a general design characteristic of XyWrite, which calls for the interpretation of keyboard macros streams, XPL program streams, and data files streams to have as much in common as possible (and probably to share a good deal of code, within XyWrite's implementation).

Here is a partial list XyWrite stream processing contexts which can be considered:

  1. for a key press, from a keyboard file key entry to the cursor location
    1. on the command line, if the cursor is on the command line
    2. in the file edit area, otherwise.
  2. in "PV" mode, from a S/G to the cursor location
    1. on the command line, if the cursor is on the command line
    2. in the file edit area, otherwise.
  3. from type 2 help frames, to the cursor location
    1. on the command line, if the cursor is on the command line
    2. in the file edit area, otherwise.
  4. in "GT" mode, from a S/G to the cursor location in the file edit area. GT mode transfers always go to the file edit area
  5. from a S/G to the XPL interpreter
  6. from the command line to S/G 00
  7. from a S/G to S/G, via the «ISxx» and «SXxx» commands
  8. from a S/G to the arithmetic expression processor, via «PVxx»
  9. from a S/G to the string expression processor, via «ISxx»
  10. from the expression processors to a S/G, via «SXxx»
The input to a XyWrite stream can consists of:
  1. normal characters, which are usually just passed as-is,
  2. 3 byte encoded characters, which may be passed as-is, but which are converted to equivalent 1 byte characters in some stream processes, such as (1) and (2) above),
  3. 3 byte function primitives which may be just passed as-is, or which may be interpreted and executed as encountered, and
  4. guillemet bracketed material, such as embedded commands.

Overview - XPL programs and "keyboard macros"

For data manipulation purposes, a "programming language" built into an text editor can be enormously powerful, because of the implicit power of the editing platform. The editor itself provides a unique and very powerful context in which the programming language operates, and for many kinds of data manipulation, this context enormously increases the power and usefulness of the language, over and above what the language's value would be without such a context.

In looking at XyWrite programmability, let's start with keyboard macros. Keyboard macros are effectively simple "programs," but which are simple enough not to require any decision making capability within the "program" -- macros are simply executed step by step, from beginning to end. The steps in a XyWrite macro correspond to things a user does at a keyboard, and can consist either of "data" characters (which, like keyboard data characters, are entered wherever the cursor is on screen) or function primitives, such as moving the cursor a character to the right.

Let's consider a file containing, say, 50 lines of an 8.3 DOS directory listing, where lines are of the form

CAB7NL    EXE       27136     9-09-97  12:00p
CLEANREG  EXE       98631     5-20-99  12:00p
CWPS-TS   ZIP      552863     3-28-97  12:00p

but where we would like to convert the filenames in the lines to read like

CAB7NL.EXE          27136     9-09-97  12:00p
CLEANREG.EXE        98631     5-20-99  12:00p
CWPS-TS.ZIP        552863     3-28-97  12:00p

If I have a "keystroke recorder" of some kind, I could perform the manipulation in a general way on the first line of the file, recording the keystrokes as I go along. If I did that in XyWrite, and transferred the keystroke recording to a file, I would have something that looks like:


First, we should note that such a macro can easily be copied into an editing window, corrections can easily be made if necessary in the editing window, and the updated result can be then be saved (in either a S/G or a file). If one records a long keyboard sequence and makes a small error along the way, the macro is generally "fixable" by this kind of editing, without having to rekey the whole sequence.

The above sequence probably doesn't look much like anything that you have seen before, so lets take a look at it in more detail. Most of what we see here are 3 byte codes for primitive functions, each being displayed by XyWrite (as we indicated earlier) as a hilighted, two letter code, followed by what appears to be a blank. The sequence looks somewhat long and obscure, but it actually represents a pretty simple sequence, given that we capture the sequence while actually operating on a sample line. Let's see what we actually did, keeping in mind that we are starting on the first (CAB7NL EXE ...) line:

XD              Unmark marked area, if one exists, so that we can mark a
                new one
LB              Make sure we are at start of line (Line Begin)
BC              Cursor to command line, clear command line (Blank
se / /XC        Enter search command to search for a blank and eXeCute
                the command
CL              Move Cursor Left over the located blank
DF              DeFine one end of area to be marked
LB              Back to the start of the line
CR CR CR CR CR  Move cursor to the right, to 1 space prior to "EXE
                (Cursor Right)
DF              DeFine other end of area to mark. We have now marked the
                entire blank area between "CAB7NL" and "EXE", except for
                the last blank in that area. And we have done so in a
                way that will also work for strings that are shorter or
                longer than "CAB7NL"
CI              Insure that we're not in insert mode (Clear Insert)
.               Overwrite the space before "EXE" with a period
XC              eXeCute search command to find the blank following
                "EXE". (The search command we used earlier is still on
                the command line, and need not be reentered.)
CL              Cursor Left over the blank found
MV              MoVe our marked area to the cursor location to the right
                of "EXE"
LB              Back to the start of the line
CD              Down to next line (Cursor Down)
Note that entering a sequence like the above could be quite error prone, if we simply tried to enter the primitive codes above into a file. But it is much simpler and less error prone if the sequence is "captured" while we are actually operating on some sample data, where we can see and verify what we are doing as we collect the keystrokes.

Once collected, we can assign this sequence to a key, so that each press of that key does the entire sequence over again. In the case of the macro indicated, each invocation of the macro operates on the next line of the file (in part, because we were thoughtful and positioned the cursor at on the next line at the end of the macro). If we press the macro invocation key 49 times, we're done. Or, to speed things a bit, we could make a longer macro by pasting the macro to a temporary file, mark (define) the pasted macro sequence, copy it three times in succession, and assigning this triple copy to a key as a macro. This key would then process three lines at a time, and we would only need to press the key 16 times to process the next 48 lines of the file.

XyWrite III+ has a "keystroke recorder" mode, where a keystroke sequence can be recorded in a file. Unfortunately, it has no mode where one can record keystrokes while actually performing them, as we postulated we could do in our example above. However, XPL programs can be and have been written (and can be found freely on the internet) which do capture and record keystrokes as they are performed (these programs capture the result in a S/G, rather than in a file). So the omission of the record-while-doing functionality within XyWrite III+ itself is not all that consequential.

So let's look again at the "macro" some more, but now focus on the representation of the macro, rather than what it actually does.


In this macro, "se / /" is a "string literal" which is simply copied to the command line. The "." is also a string literal which is copied directly to the file window. The rest of the macro consists of what are effectively "calls" to primitive functions.

In any programming language, there is a question of how string literals constants -- which can contain anything, including sequences that look exactly like "program code" -- are distinguished from the strings that actually make up the "program code". Use of enclosing quotes for string literals is a common solution. In "C", for example, the above macro (assuming similar available functionality) would look something like:

xd(); lb(); bc(); emit("se / /"); xc(); cl(); lb();
cr(); cr(); cr(); cr(); cr(); cr(); cr(); cr(); cr();
df(); ci(); emit("."); xc(); cl(); mv(); lb(); cd();

, where each "xx();" construct represents a call to some function. Note how the two literal strings -- "se / /" and "." appear as quoted strings.

The difference in representations here is very large, with the XyWrite representation actually being astonishingly compact. The compactness is achieved in part because only two kinds of "things" need be represented -- namely (1) calls to functions that have not parameters, and (2) string literal data. It also achieve because XyWrite uses a word processor notion -- namely, highlighting -- to distinguish function calls from string literal data, thus eliminating the need for artifacts like quotation marks to "set off" string literal data.

Although XyWrite's representation is very, very compact, it is not without drawbacks. One problem is that there is no provision for comments or "formatting" of the macro representation for readability -- anything in the macro that is not a primitive function call is interpreted as string literal data characters (which are nominally "emitted" during execution, wherever XyWrite's cursor is positioned on screen at the time). If you, for example, wanted to split the macro representation into lines at logical boundarys, by adding CRLF pairs to the macro representation, those CRLF pairs will, when encountered during macro execution, cause CRLF character pairs to be inserted into the current file window.

So, in the beginning at least, XyWrite macros can very hard to read. Also, the XyWrite approach makes it pretty difficult to transfer or translate a XyWrite macro to a format that can comfortably exist outside of the XyWrite editing context.

Usually, when I create a fairly large document of most any sort, I find myself creating a few keyboard macros in the process. As I create them, I copy them to the top of the file, which means that simply saving the file I'm working on preserves the macros that I have created which pertain to that file. On the next editing session for that document, the macros are then immediately available to be copied into S/Gs and executed, as needed. When the document has been completed, I simply delete the macros at the top of the file, unless I have created some of general significance that are worth saving for future use.

Keyboard macros, as we've looked at them so far, contain only 2 of the 4 ingredients commonly found in XyWrite files: 1 byte encoded characters and 3 byte encoded primitives (e.g., ). We haven't considered 3 byte encoded data characters, nor have we considered embedded (e.g., guillemet bracketed) commands.

3 byte encoded data characters won't normally occur in macros as a result of "macro recorder" output, because the XyWrite keyboard routine normally converts any 3 byte data in key definitions to 1 byte form before it is captured. But, after copying a macro into a file for editing, 3 byte encoded data characters can be use in lieu of any of 1 byte characters that are there. When the macro "plays," ALL three byte encoded data characters will be converted back to their 1 byte equivalents before being used. So, in particular, if the macro has unbalanced use of Guillemets, changing them to 3 byte versions won't hurt the way the macro "plays," but it will get rid of the XyWrite's complaining about the ill formed guillemet constructs, while you are viewing the macro as text in a file.

XyWrite's treatment of embedded commands in macros is not as simple as it might seem. If you put, say, a "«RM72» sequence in a macro, it would in fact be processed just the same as any other sequence of six 1 byte characters -- the six characters "«RM72» would simply be copied to wherever the cursor is located (command line or file window).

But, XyWrite does define a set of embedded commands -- commands that are recognized as as commands rather than simply being passed as data -- for use in "macros." The verbs associated with this special set of commands are IF, EI, LB, GL, EX, GT, PV, SX, SV, SU, XS, and RC. These commands are specifically decoded and executed when encountered in a macro. When the content of a S/G includes embedded commands from this set, we generally think of the S/G as containing an XPL program, rather than a macro.

Let's look briefly at the functionality provided by XPL's embedded commands. The main embedded commands that are used for XPL programming include:

  «IFexpr» and «EI»,   to conditionally process sequences of code. «EI»
                       is the "EndIf" programming construct
  «LBxx» and «GLxx»,   to define labels, and alter the flow of control
                       in a macro by jumping (Going To) to defined
  «EX» and «EX1»       to terminate subroutines or programs
  «GTnn» and «PVnn»,   to copy S/G nn to an editing window, or run it as
                       a macro or XPL subprogram, when these commands
                       appears as statements in an XPL program
  «SXnn,expr»          to save the result of an expression in a S/G
  «SVnn,const»         to save a string constant in a S/G
  «SUnn,const»         to save a string constant in a S/G, but also mark
                       it as an XPL program.
  «XSaa,bb,cc,dd,ee»   to extract substrings from S/G aa, with S/G bb
                       specifying a substring to be located within aa,
                       and with S/Gs cc, dd, ee returning portions of
                       the aa string to the left of, matching, and to
                       the right of the bb subtring within aa. The dd
                       output string is often the same as the bb input
                       string, but may be different if, for example, the
                       bb string contains "wildcards".
  «ISnn» and «PVnn»,   to give XPL access to S/G contents as terms in
                       expressions, to allow computations of various
                       sorts. «ISnn» gives access to S/G nn as string of
                       characters, whereas  «PVnn» gives access to S/G
                       nn as a numeric quantity
  «RC»                 to read from the keyboard
  «VAxx» and «VA$xx»   to give XPL programs access (in expressions) to a
                       variety of information, such as the current file
                       name, last error code, current margin setting,
                       and such
  «ER»                 to test for errors (e.g., search arg not found).
  «CP» and «CL»        to give programs access to the current cursor
                       location (offset in file) or cursor screen column
As is indicated, the «IF» and «SX» commands make use of "expressions." Expressions allow various computations, based on strings and/or numeric values placed and held in S/Gs, or based on information returned by the «VAxx» command (for example, «VARM» returns the right margin setting, «VA$WN» returns the number of the active window, etc.). In an expression, the contents of S/Gs can be inspected via the «ISnn» or «PVnn» commands -- «ISnn» produces the string in S/G nn, if S/G nn contained a string, and produces a "normal" string representation of the numeric value in S/G nn, if S/G nn contained a number. Similarly, «PVnn» produces the number in S/G nn, if S/G nn contained a number, or if S/G nn contains a string that "parses" as number, «PVnn» will "parse" the string and produce the corresponding number.

For strings, comparison (<, >, <=, >=, ==, <>), concatenation (+) and search () operators are provided. (xy searches string y for the substring x, and returns the position of (the first occurance of) string x within string y, or -1 if x is not found in y.) The @SIZ() function returns the length of a string, and the @UPR() function returns a copy of a string with lower case characters folded to upper case. The @CNV() function takes a 3 byte encoded primitive, and returns the two letter string which is its abbreviation.

For numeric values, comparison (<, >, <=, >=, ==, <>) and normal arithmetic (+, -, *, /) operators are provided.

Comparisons and the «ER» command produce boolean results. Boolean operators include & (AND), ! (OR), @XOR, and the @NOT() function.

Parenthesis can be used to control order of evaluation in expressions.

An «IF» statement takes a boolean expression, and conditionally causes execution of a S/G to jump past the next «EI» command, if the boolean is false. So, for example, "«IF«PV01»>«PV02»»RC «EI»" wouldl cause a character to be deleted (e.g., the RC "Rubout Character" primitive to be executed) if the numeric value held in S/G 01 is greater then the numeric value held in S/G 02.

The comparsion operators, the «ER» command, and the boolean operators are the only producers of boolean results. A boolean result cannot be saved in any direct way (e.g., in a S/G) -- it can only be used as a term in a boolean expression, or to control the conditional execution of one or more statements via use of «IF» and «EI» statements.

The expression evaluation, expression result assignment (to S/Gs, as "variables"), IF/EI conditional execution based on expression evaluation, and the LaBel and GoTo make XPL into a fully capable programming language, although not a language that even has a glimpse of "structured programming" concepts.

There are a couple of fundamental concepts in, or aspects of, XPL, that I think are conceptually quite unique, and should be understood from the outset. These are (1) the nature of the «RC» command ("Read Character from keyboard"), and what it is that this command "reads" from the keyboard, and (2) some subtleties of the XPL program/subroutine "calling" mechanisms.

The nature of the «RC» command ("Read Character from keyboard"), and what it is that this command "reads" from the keyboard was discussed above, in the section "Overview - Primitives as 'Characters'." You might want to review what was said there, now that we have explored the XPL programming language a bit.

So, now let's look at the XPL program "calling" mechanisms in more detail

As we indicated earlier, macros and XPL programs normally reside in S/Gs. Calling an XPL program or subroutine (or invoking a macro) therefore requires a "reference" to a S/G. Also, as we previously noted, there are 1072 S/Gs, in three "sets": set (a) is S/Gs 0-9 and A-Z (36 S/Gs); set (b) is &0-&9 and &A-&Z (another 36 S/Gs); and set (c) is 000-999 (another 1000 S/Gs). (000-009 are different S/Gs than 0-9; 00-99 are aliases for 000-099.)

There are two general modes in which an XPL program can "invoke" a S/G, which I will refer to respectively as "PVing" a S/G and "GTing" a S/G. When a S/G is "PVed", it is executed (interpretively) as a macro or XPL program -- that is, it is effectively called as a subroutine. If a S/G is "GTed", its contents (as a string) are simply copied (inserted) into the active file window, at the cursor location, without any kind of interpretation.

An XPL program can "PV" a S/G using the «PVxx» embedded command, or it can "GT" a S/G using the «GTxx» embedded command -- where xx in both cases specifies the S/G. The xx values allowed here, however, only allow access to S/Gs in set (a) and set (c) -- NONE of the embedded commands that access S/Gs have "addressibility" to the &0-&9 and &A-&Z set of S/Gs. Note that the «PVxx» and «GTxx» "embedded command" mechanisms are not usable in a straightforward way from within a XyWrite keyboard file.

The S/Gs in set (a) (36 S/Gs) and set (b) (36 more S/Gs) (but not the 1000 S/Gs in set (c)), can (also) be "invoked" via 72 of the available "functional primitives." Invocations via the primitives can be done either via a keyboard file entry (e.g., "59=@A" and "60=&A" would allow S/G "A" to be "invoked" via the F1 key, and S/G "&A" to be invoked via the F2 key), or via the corresponding 3 byte encoded primitives within the string that comprises a macro or XPL program.

The question that arises here, though, is: what happens when a S/G is "invoked" via a primitive? That is, is the string in the S/G interpretively executed as a program (e.g., is it "PVed"), or is the string in S/G simply copied into the active file window (e.g., is it "GTed")? When S/Gs are "invoked" via the «PVxx» or «GTxx» embedded commands in an XPL program, the program author can explicitly chose whether "PVing" or whether "GTing" is desired, by the choice of the "verb" used. When invoked via the primitives, however, there is no mechanism to specify whether the S/G is to be "PVed" or whether it is to be "GTed".

Now, XyQuest could have added another 72 primitives to the primitive set, to resolve this question. But, keep in mind that most primitives have a corresponding key assignment in the XyWrite keyboard file. Adding another 72 primitives therefore suggests that the user is going to have another 72 key definitions in the keyboard file, and is going to have another 72 key definitions to memorize.

After a little reflection, one comes the conclusion that adding another 72 primitives really isn't necessary -- a given S/G is typically either a chunk of commonly used text being held in a S/G to reduce typing or for similar such purposes, OR it is executable macro or XPL program content. So, what XyQuest did was to provide each S/G with a "flag" bit, to be set when a S/G is loaded, if the S/G is loaded with "executable" content. So, when one of the set (a) or set (b) S/Gs is "invoked" via a primitive -- either via keyboard file or via a 3 byte encoded primitive in an XPL program or macro -- it is "PVed" if the S/G is flagged, or it is "GTed" if the S/G is not flagged.

Having introduced the "flag" mechanism for S/Gs, XyQuest then went on to extend this idea to the «GTxx» embedded command method of invocation -- a «GTxx» embedded command will actually "PV" the indicated S/G, if the associated S/G is "flagged." This means that "invocation" of a S/G via a «GTxx» embedded command is fully equivalent to invocation of that S/G via a primitive -- either invocation will "PV" the S/G if the S/G is flagged, or "GT" the S/G if the S/G is not flagged.

"Flagging" of a S/Gs can be accomplished by only two mechanisms -- by loading the S/G from a file using a LDPM command, or by setting the S/G to a "constant," using the «SU» embedded command. For executable content that is constructed "on the fly" -- though the use of XPL expressions to concatenate material together, for example -- XyWrite provides no straightfoward flagging mechanism. Neither LDPM nor the «SU» command are suitable for handling dynamically created S/G content in astraightfoward way.

The lack of a more general mechanism for "flagging" a S/G as an XPL program is a significant and sometimes painful omission, because it is not that uncommon to construct XPL code "on the fly"

So, with respect to general purpose S/G access and usage, via key definitions, in macros, and in XPL programs, we would observation that S/Gs have "names", but that some kinds of names are better than others. The following table summarizes:

┌───────── 1 char name (e.g., 'N'), 36 S/Gs -------- "set (a)"
│ ┌─────── 2 char '&' name (e.g., '&K'), 36 S/Gs --- "set (b)"
│ │ ┌───── 2 or 3 digit name, (01-99), 99 S/Gs -- in "set (c)"
│ │ │ ┌─── 3 digit name (100-999), 900 S/Gs ----- in "set (c)"
┴ ┴ ┴ ┴ ────────────────────────────────────────────────────────────────
N N Y N automatically cleared when no macro or XPL pgm running
Y Y N Y value persistent when no macro or XPL pgm running
Y N N N appears in summary listing shown via keyboard or SD  primitive
Y N N N contents displayable via keyboard or SK  primtive
Y Y N N loadable from file via LDPM command
Y N N N setable from marked text via keyboard or SV  primitive
Y N N N appendable from marked text via keyboard or AD  primitive
Y N N N injectable into file window as string via keyboard or @X  type
Y Y N N invokable as XPL program or macro from keyboard or via @X  or
        &X  type primitive
Y N Y Y setable from string constant via «SUnn,[string]» and
        «SUnn,[string]» embedded commands
Y N Y Y setable from marked text via «SUnn» and «SVnn» embedded commands
Y N Y Y settable to results of string expressions, via «SX» embedded
Y N Y Y invokable as XPL program or macro via «PVnn» or «GTnn» embedded
Y N Y Y injectable into file window as string via «GTnn» embedded
Y N Y Y accessible in expressions via «IS» and «PV» embedded commands
Colors indicate: write access data read access execution access. Looking down the four columns for colored entries gives a quick overview of capabilities of the 3 different S/G "sets".

A reasonably sophisticated XyWrite user will typically find that S/G names in sets (a) and, to a slightly lesser extent, set (b) are in short supply, and are likely to be a "critical resource" to be used wisely and with some thought.

So, all of this results in a programming language, which is quite unusual to say the least. To complete our overview of keyboard macros and XPL, I'll make a few observations about the language, and then move on.

  1. Unless some care is taken, XPL programs can be about as unreadable as any programming language on the planet.
  2. Even so, XPL programs up to a certain level of complexity can be surprisingly easy to construct. Part of that is because streams collected as keyboard macros make up significant portions of many programs. It is often the case that a pretty serious overall XPL program can be constructed by generating a few keyboard macros, and then "gluing" them together into a program by adding a few «IF», «LBxx», and «GLxx» constructs to control execution flow. I don't know of many other environments where the keyboard macro "language" flows so directly into the larger "full programming language" associated with the editor.
  3. As one's XPL programming library grows, the need to use S/Gs for all programming variable requirements can become very tedious, because of the primitive "naming" (e.g., 000 to 999) scheme for S/Gs, and because there are no "private local variable" provisions for subroutines. As an XPL programmer, you should have in your mind a strategy for dealing with this problem from the beginning, or the problem will very likely overwhelm you.
  4. DOS commands can be executed by and from within XyWrite, using the command line. XyWrite is a fairly large program, so whenever a DOS program is executed from XyWrite, there will be less memory available for that program than if the program was run from command.com in the usual way. So the question arises as to whether there will typically enough memory available to execute a variety of DOS programs from within XyWrite.

    The answer seems to be that XyWrite does much better than one might expect, in this regard. XyWrite is an 182KB program, but it is capable of "unloading" portions of itself during execution to conserve memory use. It also seems to have the capability to "defragment" the memory it is using on the fly, so that all unused memory can be made contiguous and available to DOS programs that XyWrite executes.

  5. All this having been said, XPL programs are extremely versatile, and with sufficient effort, can be made to do most anything. There are even people who largely use XyWrite as their primary "shell" environment for executing other DOS program, with XPL programs being the underpinning of their shell environment.

Overview - help system

With XyWrite facilities like the keyboard file, printer files, and programmability, XyWrite is highly configurable to a user's needs and taste. XyWrite's help facility is designed with the same kind of user configurability in mind.

XyWrite's help facility starts with a XyWrite help file. The help file is a normal, editable files that is divided into "help frames." Each help frame starts with header line that looks something like


, and which is terminated by the beginning of the next frame (or by the end of the help file).

The indicated header line gives each help frame a frame type -- type "6" in this case - and one or more names -- just the name "COLUMNS" in this case. A "COLUMNS" help frame might, for example, explain XyWrite's facilities for handling columns.

The user normally selects a help file to be loaded when XyWrite starts, via the XyWrite initialization file named STARTUP.INT. The first time a help file is loaded after it has been edited, XyWrite scans the file and builds an index of the various help frames found in the file. It then tacks that index to the end of the help file. On subsequent invocations of XyWrite, XyWrite goes straight to the index, and loads only that. It is also possible that a small amount of help frame content be designated to be loaded in memory. But, generally, help frame data is accessed from disk on an as needed basis.

Help frames are much like a collection of internet web pages, in the sense that each frame can refer to others via references to frame names. This is very much like the way web pages can be linked to other web pages, and serves a the primary mechanism for help file creators to organize information so that it can be easily navigated.

There are a host of different frame types. For example, there is a type "0" frame which is designed only as an intermediate frame to facilitate navigation to more detailed help frames, and which utilizes just 2 lines on screen (a line of keywords and description line for the currently hilighted keyword) in the style of the Lotus 1-2-3 for DOS menus.

Help frames can also contain a list of available selections, with each selections being tied to either "boilerplate" data to be copied into the current file (such as a company's letterhead), or tied to XPL programs (which are contained within the help frame, but which are not displayed when the help frame is displayed) to do whatever might be desired.

The end result of the help system is that it can be used to make XyWrite into just about anything wants it to be. A minimal help file, and XyWrite is typically a compact, fast, command line driven program. In constrast, a large help file can transform XyWrite into a program that is almost completely menu driven.