Zap Documentation      Latest release     
ZapRedraw      Contact      Download     

Previous | Contents | Index | Next

Chapter 11: Commands: Zap's command language

Here we explore fully Zap's internal command language. Be warned that this gets fairly sophisticated - with this, you can effectively write your own commands to do a wide variety of things. This section is certainly unnecessary for the basic user of Zap (some might argue that it is only really useful to those seeking to bend a recalcitrant Zap to their every whim; but clearly this is not the truth :-).

You might like to look at section 9.4 first, which introduces the basic system. However we'll go over that again here in more depth, so it's not essential.

You can enter commands into Zap in a number of ways. Firstly, and most immediately, you can press cEscape and type them directly into the minibuffer; pressing Return will execute them at once. Secondly, you can bind them to a key, by entering them into the Keys file. Thirdly, you can attach them to a menu by entering them into the Menus file. See chapter 12, and in particular section 12.3.3 and section 12.3.5 for information on the last two. You can also bind commands to be automatically executed when you load a particular file or type of file - see section 12.3.2 for more information.

Throughout the rest of this section, where it matters, we will assume that you are entering commands into the minibuffer. The main difference is that errors are reported immediately (and you can't stop Zap from starting up by getting a command wrong in the minibuffer - you can sometimes in the configuration files).

If you do get something wrong, Zap will try to tell you roughly what's happened. Unfortunately, parts of Zap's command language are dealt with by the operating system, and the error messages from these bits can be a little obtuse at times. One irritating message is Expression buffer overflow, which simply means that whatever you're trying to evaluate is too long; expressions cannot be longer than 255 characters (this is set by the OS, so unfortunately we have no way of increasing it).

Remember that commands may be chained together using : (as in BASIC), and that comments may be placed after commands using ; - these run until the next :, or the end of the command string.

11.1 Types of command

Commands can take a parameter; there are four different types. These are:

You must always get the parameter type right.

Numbers may optionally be given enclosed in parentheses or quotes: WRAPWIDTH 76, WRAPWIDTH (76) and WRAPWIDTH "76" are all valid.

Strings may be given either in double quotes ", in which case additional quotes must be doubled up (so "He said ""Hi!"", then left" would be a valid string); or in parentheses, in which case quotes within the parameter must be matched. So ("Go on", said Michael) is valid, but ("Hey!) isn't. Note that ("I thought (and it was a sensible thought) that you were dead.") is also valid.

11.2 Parameter evaluation

Clearly, in some cases, you might want to do a quick calculation, and insert the result into Zap (or use it as the parameter to any command, in fact). You can do this: remember that the INSERT command takes a string parameter, and inserts that at the cursor. All we need to do is to evaluate an expression, and convert it to a string. This can be done using INSERT $(50 + 70).

This inserts the string 120 into your file.

If you need to feed the result of an evaluation to a command that takes a numeric argument, you need to make the result be a number so it's the right type for the command. The WRAPWIDTH command is just such: WRAPWIDTH #(50 + 70).

This sets the wrap width to 120 for the current file.

The bit inside the brackets is known as an expression; it is evaluated to get a result, which is then converted to the right type (string or number) to become the parameter.

(For historical reasons, you can also use @(...) instead of $(...); this is strongly deprecated, and if you start using it will almost certainly cease to be supported at the most inconvenient moment!)

11.2.1 Allowed syntax

Parameter evaluation actually uses the operating system call OS_EvaluateExpression (which is also used by *EVAL). Hence anything that can be fed to OS_EvaluateExpression is valid syntax for parameter evaluation. (In addition, Zap first substitutes the results from Zap function calls and Zap variables; we shall meet these later.)

You can either build up a string using the evaluation system, or a number. (You then convert to whatever you need using either $(...) or #(...) as described above.) Operations you can perform on numbers include:

String operations you can perform include:

Brackets can be used to force expansion and evaluation in a certain order. For instance the example INSERT $(STR(0 < 1)) above evaluates (0 < 1), which yields an integer -1 (true), and then converts to a string -1. The parameter is then converted to a string (ie: nothing is done to it) before being passed to the INSERT command to insert it at the cursor position. This sounds like a lot of work just to insert the string -1, but a few examples should help here. Only the parameter itself is given - it's assumed that you'll be using it as INSERT $(<parameter>).

These examples seem quite trivial. In the next section, we show how to use system variables (eg: <Wimp$Scrap>) within expressions.

You should beware of bugs in the operating system evaluation. For instance, INSERT $(1<2 AND 3>4) will insert 14, instead of the expected 0. In this case, you can avoid the problem by doing INSERT $(3>4 AND 1<2) - similar workarounds are almost always possible.

11.2.2 Accessing system variables

Since parameter evaluation uses the operating system routines, you can access system variables. This is done in the following way: INSERT $(sys$date).

We've given the entire command here because otherwise it might not have been obvious what was going on: anywhere that you would normally put a string (delimited by quotes) in an expression, you can put the name of a system variable and that will be inserted. Bear in mind that while most system variables are strings, some are numbers - so you can use a numeric system variable anywhere you might use a number in an expression. For instance, if you had a system variable myvar whose value was the number six (say created by *seteval myvar 6), you could do INSERT $(myvar / 2) to insert the string 3.

11.2.3 Dynamic versus static evaluation

The above examples are known as dynamic evaluation - because the expression is evaluated every single time the parameter is required. While this is generally fine when you type things into the minibuffer, it clearly might not be so helpful if you want to use it in the Keys or Menus files, where it might be being executed regularly as part of a keystroke or menu item. Evaluating the expression takes time that you'd probably rather not spend; what we need is static evaluation, that only gets evaluated once, the first time the parameter is used (in the case of the Keys or Menus files, the parameter is actually evaluated when the file is loaded).

We can do static evaluation as follows:

     WRAPWIDTH #=(50+70)
     INSERT $=(Sys$Time)
     INSERT $EVAL (Sys$Time LEFT 5)

This works exactly the same way as for dynamic evaluation - the only difference is that the different brackets force static evaluation rather than dynamic. The last example is a static function call; the first two are static evaluations to number and string respectively.

From the above examples, it may not be clear why dynamic evaluation - every single time the parameter is needed - is ever different from static. However consider that the value of system variables may change over time - and in the next two sections we will introduce functions and variables, which can be used inside evaluated parameters, and which can and will change as you use Zap. Using them, it should become clear that dynamic and static would have very different effects if the parameter say included a function call to find out where the cursor was.

Further, when we consider looping constructs, which allow a group of commands to be executed an arbitrary number of times, the difference between dynamic and static evaluation for commands typed into the minibuffer will become important as well.

11.3 Functions

Functions are a special type of Zap command; instead of performing an action, they return a value which can be used in an expression. As such, they can take any of the parameter types of normal commands. In particular, they can take evaluated expressions as parameters (although it starts to get rather difficult to find concrete and useful examples of this!).

Functions are introduced with an @ character. For instance: INSERT $(@MODEN).

This inserts the name of the current mode (in lower case).

Many functions are supplied by Zap itself, and several more by the command extensions, particularly ZapText and ZapUtil. You should see the documentation with each of these for more information, and several examples on how to use them.

For completeness, here's the best example we could find which uses only core commands and functions, and demonstrates use of a function which in turn takes an evaluated expression as a parameter: INSERT $(@CHAROFF #(my_charoff)).

This inserts the value (ie a number from 0 to 255) of the character at an offset given by the system variable my_charoff from the current cursor position. 255 is inserted if the character is outside the file (this is a feature of the @CHAR function).

11.4 Variables

Clearly if we can evaluate things, we might like to store them in variables somehow. You can do this using the SET command: SET (variable=expression).

For instance, SET (foo="string") sets the Zap variable called foo to the string value string. SET (bar=12) sets the Zap variable bar to the numeric value 12. Since we use an expression to set the value of a variable, we can perform calculations, use functions - and even use other variables (once we've shown you how to use them :-).

Variable names may contain letters, digits, and the characters `, _ and $, and must not start with a digit or $.

To unset a variable, use the UNSET command. This takes a wildcarded variable name. For instance, UNSET (*) unsets all the Zap variables; UNSET (q*x) unsets all Zap variables beginning q and ending x; UNSET (b#r) unsets all Zap variables with three characters in their names, the first being b and the last r.

In order to use a variable in an expression, prefix it with @$ or @#. Use @$ with strings, and @# with numbers. For instance: INSERT $(@$foo).

To evaluate a variable as an expression, use @=: INSERT $(@=bar).

If you need to include a literal @, you'll have to quote it unless it's in a string, eg: $(Alias$@@RunType_FFF+" this @ doesn't need to be quoted").

11.4.1 Local variables

To declare variables local, use the command LOCAL, which takes a comma-separated list of variable names: LOCAL (foo,bar).

Not that the variables are not initialised; all that happens is that their old values are stored ready to be restored when these local variables go 'out of scope'.

Local variables remain in effect ('in scope') during the current command string. (It might help to think of it as treating each command string like a procedure in BASIC.)

11.4.2 Configuration variables

In addition to the variables described above, Zap has a special set of variables called c-vars; this stands for configuration variables. These are variables which are used to configure how a command, or a particular part of Zap, operates. For instance, the StrongHelp support in ZapText uses the c-var HelpSearchPath to configure the order in which the StrongHelp manuals are searched.

Note that these are not accessible as normal Zap variables; they are mentioned here merely for completeness.

For more information on c-vars, see section 12.3.6.

11.5 Conditional constructs

We're almost into heavy territory, but don't give up, because one of the most useful features of the command language is about to be introduced: conditional constructs. These do things only if certain conditions hold, and in particular the IF construct is very useful in keystrokes.

Conditional constructs may be nested.

11.5.1 IF

The syntax of the IF construct is as follows:

     IF <condition>:<command(s)>:[ELSE:<command(s)>:]ENDIF

The ELSE bit is optional - don't use it if you don't need it. The condition for an IF is an expression, so you can put things like functions in it:

     IF (@CHAR=@TABCHAR OR @CHAR=32):RIGHT:ENDIF

Move the cursor right if it's on a tab or a space.

One of the most useful functions for this is @IN, which takes a comma-separated list of expressions. It returns true if the first element is the same as any one of the later ones (all elements must be of the same type):

     IF (@IN(@MODEN,"basic","code")=0):UPDATEWINDOW:ENDIF

Redraw the window if not in BASIC or Code mode.

11.5.2 CASE

CASE can be used instead of a whole chain of IF constructs. The syntax is:

     CASE <expression>:
             WHEN <list of expressions>:<command(s)>:
             WHEN <list of expressions>:<command(s)>:
             ...
             DEFAULT:<command(s)>:
     ENDCASE

The <list of expressions> for WHEN must be of the same type as the <expression> in the CASE command - in other words, you can either check for all strings, or all numbers, depending on what's in your CASE command.

The <list of expressions> for WHEN is comma separated; if the expression in the CASE statement matches any of those in the WHEN statement, the commands in that WHEN 'block' (up until the next WHEN) are executed. If no WHEN expressions match, the DEFAULT commands are executed.

This acts more or less identically to BASIC's CASE construct, except that if the CASE expression matches in more than one WHEN, all matching WHENs have their commands executed - unlike BASIC where only the first match is executed.

If you want functionality like C's switch() construct, then you can use CWHEN instead of WHEN; if the immediately preceding CWHEN or WHEN contained a matching expression, the commands for this CWHEN will also be executed. Otherwise CWHEN works as for WHEN.

You can break out of a CASE block using BREAK (useful if you have lots of CWHEN statements and you want to prevent fallthrough from the one you're in at the moment, but also want subsequent matching CWHEN expressions to have their commands executed).

You can break out of the CASE statement using CONTINUE (so no more WHEN or CWHEN matches will be considered).

For more information, you should read the internal help for each of these commands. For instance, to see the help on the CASE command, use HELPCOM "CASE", or c\ c "CASE".

11.6 Looping constructs

Now we consider looping. This is useful if you want to do something to, say, every line in a file, or every character in the selection. Many of these operations may already have commands dedicated to them which will almost certainly be faster - check before embarking on something complicated using Zap's command language, which can be quite slow at times.

Note that infinite loops are very easy to get into - use Alt+Escape to break out if something looks like it's taking too long and might have become stuck.

Looping constructs may be nested.

11.6.1 WHILE

WHILE <condition>:<command(s)>:ENDWHILE

The condition is a Zap expression, as you'd expect; if the condition is true, the loop is executed. On reaching the ENDWHILE command, control returns to the WHILE command and the test is done again. This continues until the condition is false. (So if the condition is false to start off with, the commands never get executed.)

11.6.2 REPEAT

REPEAT:<command(s)>:UNTIL <condition>

The condition is a Zap expression; if the condition is false, control returns to the REPEAT command. This continues until the condition is true. (So if the condition is false throughout, the commands will be executed once; contrast this to the WHILE construct, above.)

11.7 Examples

This concludes our foray into the Zap command language. To make life easier, and give some pre-built (and tested!) ways of using it to do useful things, here are a set of examples. Be warned that while the earlier ones are fairly simple, the later ones are significantly more complex.

Note that the examples are given broken over several lines - this isn't possible in Zap. It is done here simply for readability.

11.7.1 Simple conditional

Firstly, a relatively simple conditional. This does a variety of different tabbing actions depending on the mode you're in. For this, you'll need ZapUtil installed (@CURSORCOLUMN is in ZapUtil; MJE_REINDENT is in ZapMJE, but so are the C, C++ and Java modes - the command won't be needed unless we're using one of these modes, and then it will be there already).

 CASE (@MODEN):
   WHEN ("code"):
     NOTIFYUSER "Tab isn't a useful action in code mode!"
     ; WHEN, so this will now drop to the ENDCASE :
   WHEN ("c","c++"):
     IF (@CURSORCOLUMN > 0):
       TAB:CONTINUE ; Drop to the ENDCASE :
     ENDIF:
   CWHEN ("java") ; drops through from the above
     (providing the IF condition wasn't true) :
     MJE_REINDENT ; reindent the line according to context :
   WHEN ("basic"):INSERT "  " ; make it do a little spacing :
   DEFAULT : TAB ; just the normal Tab action :
 ENDCASE

In C and C++ modes, if you're not right at the left-hand edge of the window, it will perform the normal tabbing action - otherwise it will reindent the line. It will always reindent the line for Java mode; for BASIC it inserts a couple of spaces (tabbing doesn't work normally in BASIC mode - see section 11.7.4 below for how to do a more sophisticated tab in BASIC mode). In Code mode it displays a brief error message, and in all other modes it does the normal action of Tab.

11.7.2 Simple loop

Finding a simple loop without using conditionals is difficult. However one comes up: ZapDS contains a number of commands which perform bitwise operations on the byte or word at the cursor. The following EORs the selection with a given value. You'll also need ZapUtil for the functions. The rather gruesome bit of string manipulation at the beginning is to extract the window and start of the selection from @SELECTION, which also gives you the end of the selection. The function is known to return three comma-separated hexadecimal numbers, nine characters each (eight plus a leading '&'). In general it isn't advised to do things like this because of what might happen if the function changes; however in this case it's fairly guaranteed to remain constant.

(Note that really this should be wrapped in IF (@SELANYWHERE):...:ENDIF - it's rather unsafe as it is, and will fall over with an error if there isn't a selection. However, in the interests of trying to find a simple example of a loop on its own which is also useful ...)

CURSOR $(@SELECTION LEFT 19):
WHILE (@INSELECTION):
  DS_EOR "60" ; EOR with 60 :
ENDWHILE

Note that we might want to preserve the position of the cursor over this routine: you can either use marks (dropping a mark at the beginning, executing the command, and then doing LASTMARK to go back to where you were), or grab the cursor into a variable using SET (pos=@CURSOR). Then you can restore using CURSOR $(@$pos). Note that CURSOR and @CURSOR are in ZapUtil.

11.7.3 'For'-style loop and static evaluation

Zap doesn't have an equivalent of BASIC's FOR ... TO ... STEP ... NEXT (C's for(,,){}) construct. However you can simulate it using WHILE. This example also demonstrates the difference between static and dynamic expression evaluation. Note that you have to do this in two stages, because the static evaluation is done while parsing the command string - so unless the variable n is already set, you get an error.

SET (n=32)
WHILE (@#n<127):
  CHAR #(@#n)  ; dynamic :
  CHAR #=(@#n) ; static  :
  SET (n=@#n+1):
ENDWHILE

This will insert the ASCII characters from 32 (SPC - space) to 126 (~), separated by spaces. An alternate way of doing it, which can be done in one go, is to wrap the WHILE loop in a COMMAND call:

SET (n=32):
COMMAND (
  WHILE (@#n<127):
    CHAR #(@#n)  ; dynamic :
    CHAR #=(@#n) ; static  :
    SET (n=@#n+1):
  ENDWHILE
)

11.7.4 Loop and conditional

Let's try to get a better tabbing action for BASIC mode - don't forget that tabbing itself doesn't work normally in BASIC. We'd like to indent, using spaces, to the next tab stop - where there's a tab stop every eight characters. For this, you'll need ZapUtil for the @CURSORCOLUMN function.

IF (@MODEN = "basic"):
  SET (cur=@CURSORCOLUMN):
  IF ((@#cur MOD 8) = 0) ; absolutely on a tab stop - 
    insert eight spaces straight off:
    INSERT "        " :
  ELSE:
    SET (nexttab=((@#cur / 8) + 1) * 8):
    WHILE ((@CURSORCOLUMN <> @#nexttab)
           AND (@CURSORCOLUMN >= @#cur)):
      INSERT " ":
    ENDWHILE:
  ENDIF:
ENDIF

The ... AND (@CURSORCOLUMN >= @#cur) will stop us if something has gone too wrong and we wind up earlier on on the line (or a subsequent line) than we started. This could happen with wrapping, for instance. Alternatively, we could have used ... AND (@CURSORCOLUMN > 0), since if it was exactly 0, the branch which inserts exactly eight spaces would have been taken - because (0 MOD 8) = 0.

Exercise for the reader: this is actually a terribly inefficient way of doing the job. It can be done simply using INSERT, @CURSORCOLUMN, and parameter evaluation, plus the check that we're in BASIC mode at the start.


The next section in the manual is chapter 12, which looks at how to customise Zap to suit your specific needs.

Alternatively, you could move on to chapter 13, which introduces a number of useful extensions to Zap. In particular, it looks at ZapSpell (an interface to the Computer Concepts' spell checker), and introduces Olly Betts' invaluable line editor.

Previous | Contents | Index | Next


© Copyright Zap Developers 1992-2004. All Rights Reserved.