Я тут посмотрел на свои старые проги и ужаснулся, какой я быдлокодер!
Вот у меня такая неприятная ситуация с flac-декодером, как обработка ошибок. Вначале flac файла есть несколько секций с метаданными (из которых обязательна, ЕМНИП, только одна - streaminfo). Я хочу сделать так, чтобы при возникновении ошибки в чтении метаданных пользователь мог бы пропустить битые данные и читать следующие. Пока сделано так:
Определены contitions:
(define-condition flac-error ()
((message :initarg :message
:initform ""
:type string
:reader flac-error-message)))
(define-condition flac-bad-metadata (flac-error)
((metadata :reader flac-metadata
:initarg :metadata))
(:report (lambda (c s)
(format s "Bad metadata: ~A"
(flac-error-message c)))))
В любом месте, где возникает ошибка, сигнализируем flac-bad-metadata (как, например тут):
(defmethod metadata-body-reader (stream (data padding))
;; Read zero padding bytes
(let ((chunk (make-array (list (metadata-length data))
:element-type 'ub8)))
(read-octet-vector chunk stream)
;; Do sanity checks
(if (find-if-not #'zerop chunk)
(error 'flac-bad-metadata
:message "Padding bytes is not zero"
:metadata data))))
Где собираем полученные метаданные в список, делаем так:
(setq last-block
(restart-case
(let ((metadata (metadata-reader bitreader)))
(push metadata metadata-list)
(metadata-last-block-p metadata))
(skip-malformed-metadata (c)
(let ((metadata (flac-metadata c)))
(fix-stream-position bitreader metadata)
(metadata-last-block-p metadata))))
Суть в том, что чтобы из места выше по стеку, где стоит restart-case пропустить плохие метаданные, нужно с места ошибки передать эти самые метаданные (там внутри инфа о том, как восстановиться). Я передаю их в слоте condition.
Но тут возникает такая проблема. Где-то, где программист должен принимать решение, что делать с возникшими ошибками, он может написать:
(handler-bind ((flac:flac-bad-metadata
#'(lambda (c)
(invoke-restart 'flac:skip-malformed-metadata c))))
(multiple-value-setq (*a* *b*) (flac:open-flac *stream*)))
и получить список нормальных метаданных. А как ему это сделать интерактивно (через REPL/отладчик)? Суть такая, что в рестарт надо передать объект непосредственно с места ошибки, а restart-case задвинуть ниже по стеку не выйдет, иначе перемешается код читающий метаданные и код, предоставляющий стратегию восстановления.