A common source of confusion for novice and experienced KEDIT macro writers alike is knowing when to use quotes in their macros. It can sometimes appear that the rules involved are either very complex or inconsistently enforced. The rules turn out to be fairly straightforward and while you will see later in this article that there are some exceptions, the good news is that there is really one rule to keep in mind:
KEDIT commands (that could be entered from the KEDIT command line) and literal text, should be enclosed in quotes. Variable names, KEXX language keyword instructions, operators and function names are not enclosed in quotes.
These are some KEXX language keywords which, the rule states, would not be quoted:
do while if then else select trace
These are some examples of KEXX operators, so they would not be quoted:
= + - || >
These are KEXX functions, so they would not be quoted:
substr() pos() copies() focuseof()
(Note that any required parameters to the functions listed above are omitted here for simplicity.)
These are examples of some variable names so they would not be quoted:
name AverageValue J xyz
Here are some KEDIT commands, as they would appear in a macro:
"c/like/admire/ all *" "bottom" "clocate :34" "add 4"
The rule states that since these are KEDIT commands that could be entered from the KEDIT command line, they should be placed in quotes.
Literal text should also be placed in quotes as shown here:
"It was a dark and stormy night" "Wednesday" "12 Main Street"
The simplest KEDIT macros consist of a list of one or more KEDIT commands. For example, here is a three-line KEDIT macro:
"top" "down 10" "input DATA SUMMARY REPORT"
Applying the rule above you see that since these are all commands that you could enter on the KEDIT command line they should each be placed in quotes.
Your macros will likely consist of a mix of KEDIT commands, literal strings, variable names, KEXX language instructions, operators and functions. After all, this combination of KEDIT commands and programming logic is where the real value of macros lie. For example, the macro below locates each line of the file that contains "Nuthatch" and prefixes these lines with "Cavity nesting - ":
"top" "clocate :1" "set wrap off" do forever "/Nuthatch" if rc > 0 then leave "cinsert Cavity nesting - " end "top"
As you can see, some lines are in quotes and some are not. If you think about the rule though it's not hard to determine which lines should appear in quotes and which should not. These are KEDIT commands:
"top" "clocate :1" "set wrap off" "/Nuthatch" "cinsert Cavity nesting - "
They can all be entered from the KEDIT command line so the rule says to put them in quotes. By putting these in quotes, the macro interpreter simply passes the commands, exactly as they are typed between the quotes, to KEDIT to execute. This is almost always what you want to have happen when you issue a KEDIT command from a macro.
These are KEXX language instructions:
do forever if then leave end
It wouldn't make any sense to enter this on the KEDIT command line:
Rc is a variable name and, of course, > is an operator.
Let's again look at the simple macro that issues three KEDIT commands:
"top" "down 10" "input DATA SUMMARY REPORT"
This macro positions the focus line to the top of the file, moves the focus line down 10 lines and then inputs a line consisting of the text "DATA SUMMARY REPORT". What happens if the quotes are left off and you just enter the following lines in your macro?
top down 10 input DATA SUMMARY REPORT
After all, that's how you would have typed these commands on the KEDIT command line. It turns out that if you ran this macro it would work just fine. When the KEXX interpreter looks at the first line of the macro it sees that the text of this line is not in quotes so the interpreter checks to see if this matches any KEXX language keyword. Since top is not a KEXX keyword it is treated as a variable name. You presumably never assigned a value to a variable of this name in your macro so top has the uninitialized value of "TOP". (Any uninitialized variable has, as its value, the name of that variable in uppercase.) At this point the KEXX interpreter passes "TOP" to KEDIT to execute as a command which is what you want to happen.
The KEXX interpreter next compares down with the list of KEXX keywords and again does not find a match. The second line is then treated as a variable named down followed by an integer value of 10 with a blank in between them. The uninitialized value of the variable would be "DOWN". The KEXX interpreter sees this string placed next to a 10 and decides that this is a concatentation operation. Concatenation involves string values so the 10 is treated as the string "10" and the result is the string "DOWN 10". KEDIT then executes this command.
Line 3 is evaluated in the same manner with the resulting concatenated string "INPUT DATA SUMMARY REPORT" which is executed as a command by KEDIT.
The names of KEDIT commands are case insensitive so these commands:
TOP DOWN 10 INPUT DATA SUMMARY REPORT
Are completely equivalent to these:
top down 10 input DATA SUMMARY REPORT
This example seems to imply that we don't need quotes at all on KEDIT commands issued from macros so why bother? It turns out that in a lot of situations where you omit quotes, the macro works correctly anyway and this is the source of most of the confusion regarding the issue of when to use quotes. It lulls the macro writer into thinking that quotes can be skipped. The problem is that you will occasionally stumble onto something that not only fails because of the missing quotes but does so with a very perplexing error message.
Suppose you had this simple one-line macro that issues a CHANGE command and you left off the quotes:
c/Hello/Goodbye/ all *
If you ran this macro you would get this rather baffling error message:
Error 102: Not numeric value
What happened? By omitting the quotes the KEXX interpreter once again checks to see if c is a KEXX instruction. Since it is not, the interpreter treats the line as an expression and evaluates the individual components. Specifically, it sees this line as being made up of these pieces:
c / Hello / Goodbye / all *
The forward slashes end up being interpreted as division signs, the asterisk as multiplication and c, hello, goodbye and all are treated as variables. The first thing the KEXX interpreter tries to do is divide the value of the variable c by the value of the variable hello. You can't divide the string "C" by the string "HELLO", of course, hence the error message. This is quite a bit different from the CHANGE command that you had intended. At least now it should be clear as to why you got the message about "Not numeric value".
Suppose you had a macro that accepted an argument that was either the string "YES" or "NO". For this example, your macro will use this parameter to decide whether you want to uppercase or lowercase the contents of the file. The macro might look like this:
parse arg uppercase if uppercase = "YES" then "uppercase all" else "lowercase all"
What happens if we omit the quotes like this?
parse arg uppercase if uppercase = YES then uppercase all else lowercase all
If you were to invoke this macro and pass a "YES" as a parameter then the PARSE ARG instruction would have the effect of assigning the value "YES" to the variable uppercase. How does that affect the macro with the missing quotes? This line:
if uppercase = YES then uppercase all
will be interpreted very differently than you might think. If is a keyword, the first uppercase is a variable name and then is another keyword. Since YES is no longer in quotes it is also treated as a variable and has the value "YES". So far so good. But the command issued to KEDIT if the uppercase variable equals "YES" would be this:
By leaving uppercase all unquoted, the macro interpreter saw this as the value of the variable uppercase concatenated with the value of the variable all. But the PARSE statement has assigned the uppercase variable the value of "YES". When the resulting YES ALL command is issued you get the error message:
Error 21: Invalid command: yes
It might take a little while to figure out what is causing this error but at least you get an error message to clue you in that there is a problem.
Suppose you wrote the macro so that the expected parameter was a zero or a one rather than "YES" or "NO". The KEXX macro language interprets a one as a Boolean "true" and a zero as "false". So the macro might look like this:
parse arg uppercase if uppercase then "uppercase all" else "lowercase all"
This works just fine but what happens if we omit the quotes again like this?
parse arg uppercase if uppercase then uppercase all else lowercase all
If you were to invoke this macro and pass a 1 as a parameter then the uppercase variable would be assigned a 1. How does that affect the macro with the missing quotes? This line:
if uppercase then uppercase all
is again interpreted as the keyword instruction if, the variable uppercase, the keyword then, the variable uppercase and the variable all. When this is interpreted it results in this:
if 1 then "1 ALL"
The command that is issued is 1 ALL. KEDIT allows you to enter a target followed by a command. The number 1 is a valid target and tells KEDIT to shift the focus line down one line in the file. ALL with no operands does nothing since presumably all of your file was selected. That means that 1 ALL is perfectly legal, though useless in this context. Not only is the file not uppercased but there is no error message at all! Problems like this can sometimes be very difficult to figure out and debug.
You've seen that omitting quotes in macros can sometimes get you into trouble. The best approach to avoiding this is to always use quotes when the rule tells you to. This is defensive programming. Rather than react to a problem after it occurs by adding quotes, be proactive and always use quotes to avoid the problem in the first place.
As mentioned at the beginning of this article, the rule for when and when not to use quotes has some exceptions.
Placing a KEDIT command in quotes ensures that the command will be passed to KEDIT to execute, exactly as it is specified within the quotes. There are times though when a KEDIT command should not be placed entirely in quotes because you want to use the value of a variable to build the command on-the-fly. You would generally combine quoted literal text with one or more un-quoted variables. When the macro runs the macro interpreter concatenates the literal text with the value of any specified variables and the resulting string is passed to KEDIT to execute as a command. For example, suppose you wanted to invoke a macro and pass a line number to it. The macro would move to this line before proceeding as in this example:
parse arg LineNum * Use a default value of line #1 if LineNum = "" then LineNum = "1" * Adjust the focus line position ":"LineNum . . .
If no argument is passed to the macro it defaults to line 1 of the file. If you passed 125 to the macro then the LineNum variable is assigned this value in the PARSE statement. The expression:
is interpreted as the literal string ":" concatenated with the value of LineNum with no blank placed between the two. The result is ":125" and this resulting string is passed to KEDIT to execute. That makes line 125 the focus line.
Suppose you wanted a macro to remove formfeed characters (ASCII 12) from a file. You could do this:
"c/// all *"
The black rectangular block in this example is what you would see in KEDIT for Windows if you held down the Alt key, typed 0-1-2 on the numeric keypad and then released the Alt key. In text mode KEDIT, you would see this symbol: . The macro will work correctly, but if you print this macro your printer will interpret the hardcoded formfeed character and do a page eject in the middle of that line. This approach gets around the print problem:
"c/"d2c(12)"// all *"
The macro interpreter will concatenate "c/" to the value returned by the d2c(12) function call (the formfeed character in this example) and further concatenate this to "// all *". The resulting string is passed to KEDIT to issue as a command. If you print the macro code there is no embedded formfeed character in the code so there is no unwanted page eject.
When should you use single quotes and when should you use double quotes? Actually, either quote mark works to denote literal text as long as you use the quotes in pairs. The quoted text needs to begin and end with the same quote mark. For example:
"c/xyz/abc/ all *" 'down 10' name = "John"
Say you wanted to assign this text:
The quote mark (") is used to denote literal text
to the variable remark. Here the text of the literal string itself contains a double quote character. If you enclose the text in double quotes like this:
remark = "The quote mark (") is used to denote literal text"
you will get an error when the macro runs. That's because the macro interpreter sees the string start with a double quote and the interpreter scans from there to find the first closing double quote. That happens to be the one in the parentheses which isn't what you had intended. You can get around this by enclosing the entire string in a pair of single quotes:
remark = 'The quote mark (") is used to denote literal text'
While the above technique works just fine when you want to specify a literal string that contains a quote character it's not the only way to accomplish this. The KEXX macro language allows you to specify a quote character within a quoted string and use only single or double quotes. Here is an example where the string contains a double quote character and the entire string is also enclosed in double quotes:
remark = "The quote mark ("") is used to denote literal text"
When you double a quotation character within a string it is interpreted as a single occurrence of the quotation character. So in this situation the macro interpreter scans the string looking for the closing double quote and when it finds two double quotes in a row it treats these as just one occurrence of the double quote embedded in the string. The interpreter continues scanning for the closing double quote.
In practice you rarely need to use this last method of embedding quote characters within a literal string. Here is an example where you would actually have to use it though:
remark = 'Mr. O''Donnolly was heard to exclaim, "Yikes!"'
Here the literal text contains both a single quote and a pair of double quotes!
If you use the DEFINE command in your profile macro to set up simple, one-line key macros you will likely need to use both kinds of quote marks. For example, this sets up the F5 key to locate the line of the file that has "Data Log" in it and then displays the message "Enter log data...":
"define f5 '/Log Data';say 'Enter log data...'"
You can reverse the quotes like this:
'define f5 "/Log Data";say "Enter log data..."'
Why can't you just do this?
'define f5 /Log Data;say Enter log data...'
Once again, DEFINE is a KEDIT command and the rule says to enclose the command in quotes which is exactly what was done in the example above. This DEFINE command is defining a macro though and that macro itself issues KEDIT commands which should also be in quotes. This DEFINE command is equivalent to running this .KEX file when the F5 key is pressed:
'/Log Data' say 'Enter log data...'
If you omitted the quotes here you would get an error from line 1 since the macro interpreter would see the "/" character as division. The result is that we need the two layers of quotes when using the DEFINE command if the macro being defined issues KEDIT commands.
The SET NOVALUE command can help you locate unquoted text in your macros. You have seen that when you omit quotes the macro interpreter can treat the components as though they were variable names. These variable names are often uninitialized since you had not intended for them to be interpreted as variables in the first place. SET NOVALUE controls how the KEDIT macro interpreter handles uninitialized variables. With the default of SET NOVALUE OFF in effect, using uninitialized variables can go relatively un-noticed. By setting NOVALUE ON though, the macro interpreter generates an error as soon as you try to use an uninitialized variable. Simple KEDIT commands like this:
will generate an error with SET NOVALUE ON in effect if the variable down has not been assigned a value yet.
SET NOVALUE affects all macros. If SET NOVALUE is set ON then any macro using an uninitialized variable will generate an error message.