Previous chapter
·
Next chapter
·
Table of Contents
You are probably well aware of the diversity of potential errors when writing computer programs. They range from simple typographical errors made while entering a program, to subtle design problems which may only be revealed by unexpected input data.
Debugging a SNOBOL4 program is not fundamentally different than debugging programs written in other languages. However, SNOBOL4's syntactic flexibility and lack of type declarations for variables produce some unexpected problems. By way of compensation, an unusually powerful trace capability is provided.
Of course, there may come a time when you can't explain your program's behavior, and decide "the system" is at fault. No guarantee can ever be made that SNOBOL4 is completely free of errors. However, its internal algorithms have been in use in other SNOBOL4 systems since 1967, and all known errors have been removed. Often the problem is a misunderstanding of how a function works with exceptional data, and a close reading of the reference section clears the problem up. In short, suspect the system last.
Compilation errors are the simplest to find; SNOBOL4 displays the erroneous line on your screen with its statement number, and places a marker below the point where the error was encountered. The source file name, line number, and column number of the error are displayed for use by your text editor. Only the first error in a statement is identified, so you should also carefully check the remainder of the statement. A typical line looks like this:
32 ,OUTPUT = CNT+ 1
^
test.sno(57,10) : Compilation Error : Erroneous statement
Here, the comma preceding the word OUTPUT is misplaced. The
message indicates that ",OUTPUT" is not a valid language element.
Programs containing compilation errors can still be run, at least until a statement containing an error is encountered. When that happens, SNOBOL4 will produce an execution error message, and stop.
A complete description of error messages is provided in Chapter 9 of the Reference Manual, "System Messages."
Once a program compiles without error, testing can begin. Two kinds of errors are possible: SNOBOL4 detectable errors, such an incorrect data type or calling an undefined function, and program logic errors that produce incorrect results.
With the first type of error, you'll get a SNOBOL4 error message with statement and line numbers. Inspecting the offending line will often reveal typing errors, such as a misspelled function name, keyword, or label. If the error is due to incorrect data in a variable -- such as trying to perform arithmetic on a non-numeric string -- you'll have to start debugging to discover how the incorrect data was created. Placing output statements in your program, or using the trace techniques described below, will usually find such errors.
Here are some common errors to look for first:
PUTPUT = LINE1
creates a new variable and assigns LINE1 to it. Worse still
is using a misspelled name as a value source, since it will
return a null string value.
The first type of error is relatively easy to find -- produce an end-of-run dump by using the SNOBOL4 command line option /D. You can study the list of variables for an unexpected name. The second type of error is naturally much harder to find, because variables with null string values are omitted from the end-of-run dump. In this case, you will have to study the source program closely for misspellings.
LINE = TRIM (INPUT)
is not a call to the TRIM function. The blank between TRIM
and the left parenthesis is interpreted as concatenating
variable TRIM with the expression (INPUT). TRIM used as a variable is likely to be the null string, so INPUT is returned unchanged.
X = Y -Z
concatenates Y with the expression -Z.
N = INPUT
IDENT(N, 3) :S(OK)
are not correct. N contains a string, which is a different
data type from the integer 3. This could be corrected by
using IDENT(+N, 3), or EQ(N, 3). Once again, &TRIM should
be 1, or the blanks appended to N will prevent its conversion
to an integer.
NEXTWRD LINE WRDPAT = :F(READ)
However, by omitting the equal sign we would repeatedly find
the same first word in LINE:
NEXTWRD LINE WRDPAT :F(READ)
RESULT = CONVERT(TALLY, "ARRAY")
RESULT will not be set if CONVERT fails, and a subsequent
array reference to RESULT would produce an execution error.
INPUT SPAN('0123456789') . N :F(EOF)
In the latter case, if we want to generate an error message,
the statement should be split in two:
N = INPUT :F(EOF)
N SPAN('0123456789') . N :F(WARN)
N = EQ(REMDR(N,2),0) 'EVEN' | 'ODD'
We note in passing that SNOBOL4+, Catspaw's professional
SNOBOL4 package, provides language extensions that allow
just that:
N = (EQ(REMDR(N,2),0) 'EVEN', 'ODD')
S TAB(49) LEN(1) = '*'
we would find the first 50 characters replaced by a single
asterisk. Instead, we should say:
S POS(49) LEN(1) = '*'
or, even more efficiently:
S TAB(49) . FRONT LEN(1) = FRONT '*'
NTH_CHAR = POS(*N - 1) LEN(1) . CHAR
will copy the Nth subject character to variable CHAR. The
pattern adjusts automatically if N's value is subsequently
changed. Omitting the asterisk would capture the value of N
at the time the pattern is defined (probably the null
string).
These simple methods should find a majority of your bugs:
More subtle errors can be pinpointed using SNOBOL4's trace facility, described below.
Tracing the flow of control and data in a program is usually the best way to find difficult problems. SNOBOL4 allows tracing of data in variables and some keywords, transfers of control to specified labels, and function calls and returns. Two keywords control tracing: &FTRACE and &TRACE.
Keyword &FTRACE is set nonzero to produce a trace message each time a program-defined function is called or returns. The trace message displays the statement number where the action occurred, the name of the function, and the values of its arguments. Function returns display the type of return and value, if any. Each trace message decrements &FTRACE by one, and tracing ends when &FTRACE reaches zero. A typical trace messages looks like this:
STATEMENT 39: LEVEL 0 CALL OF SHIFT('SKYBLUE',3),TIME = 140
STATEMENT 12: LEVEL 1 RETURN OF SHIFT = 'BLUESKY',TIME = 141
The level number is the overall function call depth. The program
execution time in tenths of a second is also provided.
Keyword &TRACE will also produce trace messages when it is set nonzero. However, the TRACE function must be called to specify what is to be traced. Tracing can be selectively ended by using the STOPTR function. The TRACE function call takes the form:
TRACE(name, type, string, function)
The name of the item being traced is specified using a string
or the unary name operator. Besides variables, it is also possible
to trace a particular element of an array or table:
TRACE('VAR1', ...
TRACE(.A<2,5>, ...
TRACE('SHIFT', ...
"Type" is a string describing the kind of trace to be performed.
If omitted, a VALUE trace is assumed:
| 'VALUE' | Trace whenever name has a value assigned to it. Assignment statements, as well as conditional and immediate assignments within pattern matching will all produce trace messages. |
| 'CALL' | Produce a trace whenever function name is called. |
| 'RETURN' | Produce a trace whenever function name returns. |
| 'FUNCTION' | Combine the previous two types: trace both calls and returns of function name. |
| 'LABEL' | Produce a trace when a GOTO transfer to statement name occurs. Flowing sequentially into the labeled statement does not produce a trace. |
| 'KEYWORD' | Produce a trace when keyword name's value is changed by the system. The name is specified without an ampersand. Only keywords &ERRTYPE, &FNCLEVEL, &STCOUNT, and &STFCOUNT may be traced. |
TRACE(.T<"zip">, "VALUE", "Table entry 'zip'")
The last argument, function, is usually omitted. Its use is
described in the next section.
The form of trace message displayed for each type of trace is listed in Chapter 9 of the Reference Manual, "System Messages."
Each time a trace is performed, keyword &TRACE is decreased by one. Tracing stops when it reaches zero. Tracing of a particular item can also be stopped by function STOPTR:
STOPTR(name, type)
Normally, each trace action displays a descriptive message, such as:
STATEMENT 371: SENTENCE = 'Ed ran to town',TIME = 810
Instead, we can instruct SNOBOL4 to call our own programdefined
function. This allows us to perform whatever trace
actions we wish. We define the trace function in the normal way,
using DEFINE, and then specify its name as the fourth argument of
TRACE. For example, if we want function TRFUN called whenever
variable COUNT is altered, we would say:
&TRACE = 10000
TRACE('COUNT', 'VALUE', , 'TRFUN')
DEFINE('TRFUN(NAME,ID)') :(TRFUN_END)
. . .
TRFUN will be called with the name of the item being traced,
'COUNT', as its first argument. If a third argument was provided
with TRACE, it too is passed to your trace function, as ID.
(Here the argument was omitted.) To use trace functions effectively,
we must pause to describe a few more SNOBOL4 keywords:
| &LASTNO | The statement number of the previous SNOBOL4
statement executed.
|
| &STCOUNT | The total number of statements executed.
Incremented by one as each statement begins
execution.
|
| &ERRTYPE | Error message number of the last execution
error.
|
| &ERRLIMIT | Number of nonfatal execution errors allowed before SNOBOL4 will terminate. |
Now, let's consider debugging a program where variable COUNT is inexplicably being set to a negative number. Continuing with the previous example, the function body would look like this:
&TRACE = 10000
TRACE('COUNT', 'VALUE', , 'TRFUN')
DEFINE('TRFUN(NAME,ID)TEMP') :(TRFUN_END)
TRFUN TEMP = &LASTNO
GE($NAME, 0) :S(RETURN)
OUTPUT = 'COUNT negative in statement ' TEMP :(END)
TRFUN_END
The first statement of the function captures the number of the
last statement executed -- the statement that triggered the trace.
We then check COUNT, and return if it is satisfactory. If it is
negative, we print an error message and stop the program.
When a trace function is invoked, keywords &TRACE and &FTRACE are temporarily set to zero. Their values are restored when the trace function returns. There is no limit to the number of functions or items which may be traced.
Tracing keyword &STCOUNT will call your trace function before every program statement is executed.
Program CODE.SNO traces keyword &ERRTYPE to trap nonfatal execution errors from your sample statements, and produce an error message. Keyword &ERRLIMIT must be set nonzero to prevent SNOBOL4 from terminating when an error occurs.
To a greater extent than other languages, SNOBOL4 programs are sensitive to programming methods. Often, there are many different ways to formulate a pattern match, and some will require many more match attempts than others.
As you work with SNOBOL4, you will develop an intuitive feel for the operation of the pattern matcher, and will write more efficient patterns. I can, however, start you off with some general rules:
Efficiency should not be measured purely in terms of program execution time. With the relatively low cost of microcomputers, the larger picture of time spent designing, coding, and debugging a program also must be considered. A direct approach, emphasizing simplicity, robustness, and ease of understanding usually outweighs the advantages of tricky algorithms and shortcut techniques. (But we admit that tricky pattern matching is fun!)
Previous chapter
·
Next chapter
·
Table of Contents