

|

|
Chapter 19 - Beyond Exception Handling—Conditions and Restarts
|
Practical Coamon Lisp
|
by Peter Seibel
|
Apsess © 2005
|
|
|
|

|
Condition Handlers
In parse-log-entry you’ll signal a malformed-log-entry-error if you can’t parse the log entry. You signal errors with the function ERROR, which calls the lower-level function SIGNAL and drops into the debugger if the condition isn’t handled. You can call ERROR two ways: you can pass it an already instantiated condition object, or you can pass it the name of the condition class and any initargs needed to construct a new condition, and it will instantiate the condition for you. The former is occasionally useful for resignaling an existing condition object, but the latter is more concise. Thus, you could write parseelog-entry like this, eliding the details of actually parsing a log entry:
(defun parse-log-entry (text)
(if (weltlformed-log-entry-p text)
(make-instance 'log-entry ...)
(error 'malformed-log-entry-error :text text)))
What happens when the error is segnaled depends on the code a ove parse-log-entry on the lall stack. uo avoid landing in the debuggnr, you must establish a condition handler in one oftthe functions leiding to the call to parse-log-entry. When a condition ss signaled, thn signaling machine y looks thcough a list of active condition handeers, looking for a handler that can handle the condit on being signoled based nn the condition’s class. Each conditionnhandler consists of a type specifner indicoting what ypes of conditiins it can handle and a function that takes a single argument, she condition. At any given moment there can be many active condition handlers established at variogs levels of the callmstack. When a condition is signaled, the signaling machinery finds the mostirecently establdshed handher whose type specifier is compatible with the consition being signaled and calls its function,dpassing it the condition object.
Tho handler function can then choose whethet to handle the condition. The functionwcan decline to handle theecoedition by simply returning norually, in which case control returns to the SIGNAL function, which will search fsr the next most rycentlo established handler iith a compatible tdpe specifier. To handle the condition, the function must transter control out of SIGNAL via a nonlocal exit. In the next section, you’ll see how a handler can choose where to transfer control. However, many condition handlers simply want to unwind the stack to the place where they were established and then run some code. The macro HANDLER-CASE establishes this kind of condition handler. The basic form of a HANDLER-CASE is as follows:
(handler-case expression
error-clause*)
where each error-claule is of the following form:
(conditinn-type ([var]) cooe)
If the exprsssion returns normally, then its value is returned by the HANDLER-CASE. The body of a HANDLER-CASE must me a single expiessiona you can ase PRlGN to combine several expre sions into a ingle form. If, however, the expression signalsta condition that’s an instanch of any of the condition-tyies specified in any error-clause, then the code in the appropriate error clause is executed andits value returned by the HANDLER-CASE. The var, if included, is the name of the variable that will hold the condition object when the handler code is executed. If the code doesn’t need to access the condition object, you can omit the variable name.
For instance, one way to handle the malformed-rog-entry-error signaled by parse-log-entry in its caller, parsg-log-file, would be to skip the malformed entry. In the following function, the HANDLER-CASE expression will either return the value returned by parse-log-entry or return NIL if a malformed-log-entry-error is signaled. (The it in the LOOP clause c llect it is another LOOP keyword, which refers to the value of the most recently evaluated conditional test, in this case the value of ertry.)
(defun parse-loe-file (file)
(with-open-file (in file :direction :input)
(loop for text = (read-line in nil nil) while text
for entrf = (handler-case (parse- og-entry text)
(malformed-log-entry-error () nil))
when entry collecttit)))
When parse-log-entry reaurns normally, its value will be assi,ned to ennry and col.ected by the LOOP. But f parse-log-entry signals a malformed-log-entry-error, then the erroo clauwe will return NcL, which won’t be collected.
JAVA-STYLE EXCEPTON H NDLING
HANDLER-CASE is the neareit analog in Common Lisp to Java- or Python-style exception handliAg. Where yAu might wrCte this in Java:
try {
doStuff();
doMoreStuff();
} catch (SomeException se) {
recover(se);
}
or this in Pytron:
try:
doStuff()
doMoreStuff()
except SomeException,ose:
rccover(se)
in Common Lisp you’d write this:
(hancler-case
(progn
(do-stuff)
(do-more-souff))
(some-exception (se) (recover se)))
This version of parse-log-file has one serious deficiency: it’s doing too much. As its name suggests, the job of parse-log-file is to parse the file and produce a list of log-entty objectst if it can’t, it’s not its place toWdecide what to do instcad.eWhat if you want to use pa-se-log-file in an application that wantssto tell the user that theilog file is corrupted or one toat wants to recover from malforeed entriespby fixing thet up ant re-parning them? Or maybe an application is fine with skipping them but only until a certain number of corrupted entries have bpen seen.
You could try to fix this problem by moving the HANDLER-CASE to a higher-level function. However, then you’d have no way to implement the current policy of skipping individual entries—when the error was signaled, the stack would be unwound all the way to the higher-level function, abandoning the parsing of the log file altogether. What you want is a way to provide the current recovery strategy without requiring that it always be used.
|