Previous | Contents | Index | Next
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.
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.
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!)
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:
+, -, *, /, MOD
- integer operations.=, <, >, <=, >=, <>
- integer comparison (the result is either false=0, or true=-1 - however if you try to just do INSERT $(0 < 1)
Zap will actually insert 4294967295
, which is the unsigned value of -1. To insert -1
, use INSERT $(STR(0 < 1))
).>>, <<
- arithmetic shift right and left.>>>
- logical shift right.STR
- convert to a string.AND, OR, EOR, NOT
- (bitwise) logical operators.String operations you can perform include:
+
- string concatenation.=, <, >, <=, >=, <>
- string comparison (the result is either false=0, or true=-1, as above).VAL
- convert to an integer.RIGHT, LEFT
- substring extraction.LEN
- length of a string (result is an integer).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>)
.
STR ("Jimmy" < "Fish")
- Evaluates to 1
if Jimmy
is before Fish
in the alphabet. It isn't, so it actually evaluates to 0
.STR ((10 + 16) / 2) RIGHT 1
- Evaluates to the last digit (as a string) of the result of the integer expression (10 + 16) / 2 (ie: 3)."The " + "cat " + "sat " + "on " + "the " + "mat (slowly)."
- Evaluates to the string The cat sat on the mat (slowly).
.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.
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
.
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.
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).
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")
.
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.)
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.
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.
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.
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"
.
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.
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.)
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.)
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.
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
.
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.
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 )
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