Next: Restarts, Previous: Condition Object, Up: Common Lisp Conditions [Contents][Index]
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:
The former is occasionally useful for resignaling an existing condition object,
but the latter is more concise. Thus, you could write parse-log-entry
like
this:
(defun parse-log-entry (text) (if (well-formed-log-entry-p text) (make-instance 'log-entry ...) (error 'malformed-log-entry-error :text text)))
What happens when the error is signaled depends on the code above
parse-log-entry
on the call stack. To avoid landing in the debugger, you must
establish a condition handler in one of the functions leading to the call to
parse-log-entry
. When a condition is signaled, the signaling machinery
looks through a list of active condition handlers, looking for a handler that
can handle the condition being signaled based on the condition’s class.
Each condition handler consists of:
At any given moment there can be many active condition handlers established at various levels of the call stack. When a condition is signaled, the signaling machinery finds the most recently established handler whose type specifier is compatible with the condition being signaled and calls its function, passing it the condition object.
The handler function can then choose whether to handle the condition or decline it.
Many condition handlers simply want to unwind the stack to the place where they were established and then run some code.f
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-clause’ is of the following form:
(condition-type ([var]) code)
HANDLER-CASE
. The body of a HANDLER-CASE
must be a single expression; you
can use PROGN
to combine several expressions into a single form.
HANDLER-CASE
.
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-log-entry-error’ signaled by
parse-log-entry
in its caller, parse-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.
(defun parse-log-file (file) (with-open-file (in file :direction :input) (loop for text = (read-line in nil nil) while text for entry = (handler-case (parse-log-entry text) (malformed-log-entry-error () nil)) when entry collect it)))
(The it
in the ‘LOOP’ clause collect it
is another ‘LOOP’ keyword, which
refers to the value of the most recently evaluated conditional test, in this
case the value of entry
.)
When parse-log-entry
returns normally, its value will be assigned to entry
and collected by the ‘LOOP’. But if parse-log-entry
signals a
‘malformed-log-entry-error’, then the ‘error’ clause will return ‘NIL’, which
won’t be collected.
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-entry’ objects; if it can’t, it’s not its place to
decide what to do instead. What if you want to use parse-log-file
in an
application that wants to tell the user that the log file is corrupted or one
that wants to recover from malformed entries by fixing them up and re-parsing
them? Or maybe an application is fine with skipping them but only until a
certain number of corrupted entries have been 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.
Next: Restarts, Previous: Condition Object, Up: Common Lisp Conditions [Contents][Index]