Обработка исключений
Исключение - это отклонение программы от маршрута "нормального" выполнения. Пакет exception из состава PFC содержит предикаты помогающие обрабатывать и распространять исключения, которые возникают в программах на Прологе. Это руководство объясняет:
- Как обрабатывать исключения;
- Как возбуждать ваши собственные исключения;
- Как продолжать другое исключение.
Как перехватывать исключения
Рассмотрим программу, которая читает текстовый файл и выводит его содержимое на консоль. PFC предлагает предикат file::readString, который позволяет выполнить эту задачу. Однако, некоторые обстоятельства могут помешать эту задачу; например, заданное имя файла ссылается на несуществующий файл. Когда предикат file::readString не может выполнить задачу, он генерирует исключение.
Visual Prolog предлагает конструкцию try/catch (см. Try вместо Trap), которая перехватывает исключение и позволяет его обработать. Детальное описание этой конструкции можно найти в справке по Visual Prolog (Language Reference -> Exception Handling).
Соответственно, код для перехвата исключения будет выглядеть так:
try MyTxtFileContent = file::readString(myFileName, _IsUnicode) catch ErrorCode do handleFileReadError(ErrorCode) end try, ...
Самое интересное здесь это - имплементация предиката handleFileReadError:
class predicates handleFileReadError : (exception::traceID TraceId) failure. clauses handleFileReadError(TraceId):- Descriptor = exception::tryGetDescriptor(TraceId, fileSystem_api::cannotcreate), !, % файл не может быт загружен exception::descriptor(_ErrorCode, % ErrorCode используется, если пользователь сам вызывает errorExit. _ClassInfo, % информация о классе, который возбудил исключение. _Exception, % на самом деле это fileSystem_api::cannotcreate, % но параметр не может участвовать в сравнении с помощью ' = '. % смотрите exceptionState::equals _Kind, % исключение может быть возбуждено или продолжено ExtraInfo, _TraceId, % здесь идентификатор ошибки, но необходимости в проверке нет _GMTTime, % время возникновения исключения ExceptionDescription, _ThreadId) = Descriptor, FileName = namedValue::mapLookUp(ExtraInfo, fileSystem_api::fileName_parameter, string("")), Reason = namedValue::mapLookUp(ExtraInfo, common_exception::errorDescription_parameter, string("")), stdIO::write("Невозможно загрузить файл по причине: ",ExceptionDescription, "\nИмя файла: ", FileName, "\nПричина: ", Reason ), fail. handleFileReadError(ErrorCode):- isDebugMode = true, % режим отладки !, exceptionDump::dumpToStdOutput(ErrorCode), % выводим на консоль, поскольку режим отладки fail. handleFileReadError(_ErrorCode):- % программа не может обработать исключение и не информирует о нем fail.
Когда предполагается исключение fileSystem_api::cannotcreate, предикат exception::tryGetDescriptor вызывается именно с этим параметром для перехвата и обработки такого исключения. В нашем случае используется вывод на консоль:
stdIO::write("Невозможно загрузить файл по причине: ", ExceptionDescription, "\nИмяФайла: ", FileName, "\nПричина: ", Reason ),
с объяснением причины исключения.
Обратите внимание, что нет необходимости в очистке следов исключения, поскольку вся необходимая информация сохраняется в TraceId. Этот факт значительно ускоряет работу программы.
Полный пример содержится в проекте catchException\catchException.prj6.
Как вызвать исключение
Рассмотрим программу, которая проверяет размер заданного файла. PFC предлагает предикат file::getFileProperties, который возвращает размер заданного файла. Если файл имеет нулевой размер, то мы можем вызвать исключение с помощью предиката exception::raise. Необходимо при этом создать предикат, который будет характеризовать исключение. С этой целью мы создаём предикат myExceptionZeroFileSize. Код, вызывающий исключение, будет выглядеть так:
clauses run():- console::init(), try file::getFileProperties(myFileName,_Attributes,Size,_Creation,_LastAccess,_LastChange) catch ErrorCode do handleFileGetPropertiesError(ErrorCode) end try, Size = unsigned64(0, 0), % file size is zero !, exception::raise ( classInfo, myExceptionZeroFileSize, [namedValue(fileSystem_api::fileName_parameter, string(myFileName))] ). run().
Первый параметр предиката exception::raise - предикат classInfo, который генерируется средой IDE для каждого нового класса. Для генерируемого исключения неплохо было бы определить дополнительную информацию, относящуюся к данному исключению. В нашем случае полезно имя файла, поскольку нулевой размер связан с этим заданным файлом.
Предикат myExceptionZeroFileSize создаётся по шаблону:
clauses myExceptionZeroFileSize ( classInfo, predicate_Name(), "Размер файла не может быть нулевым" ).
Здесь первый и второй параметры предикатов исключений всегда есть classInfo и predicate_Name() соответственно, а третий параметр содержит текстовое пояснение причины исключения.
Полностью пример представлен в проекте raiseException\raiseException.prj6.
Продолжение другого исключения
Рассмотрим программу, помещённую в DLL, которая читает текстовый файл и его содержимое передаёт в возвращаемом параметре. Если происходит исключение, то DLL продолжает его и добавляет информацию о версии DLL. Для продолжения исключения код выглядит так:
constants dllVersion = "1.20.0.0". clauses loadFile(FileName) = FileContent :- try FileContent = file::readString(FileName, _IsUnicode) catch ErrorCode do exception::continue(ErrorCode, classInfo, continuedFromDll, [namedValue(version_Parameter,string(dllVersion))]) end try.
Предикат continuedFromDll объявлен как:
predicates continuedFromDll : core::exception as "_ContinuedFromDll@12"
и этот предикат экспортируется из DLL. Имплементация предиката continuedFromDll создаётся по шаблону (аналогично предикату myExceptionZeroFileSizeabove):
clauses continuedFromDll ( classInfo, predicate_Name(), "Exception continued from continueException.DLL" ).
При продолжении исключения предикат exception::continue добавляет информацию о версии DLL.
Полностью пример представлен в проекте continueException\continueException.prj6.
Посмотрим теперь, как перехватывать такого рода исключения. Код в принципе похож на обсуждавшийся выше, поэтому мы сосредоточимся на различиях.
constants myFileName = "my.txt". clauses run():- console::init(), initExceptionState(exception::getExceptionState()), try MyTxtFileContent = loadFile(myFileName) catch TraceID do handleFileReadError(TraceID) end try, !, stdIO::write("The content of ",myFileName,"is:\n",MyTxtFileContent). run(). class predicates handleFileReadError : ( exception::traceID TraceId )failure . clauses handleFileReadError(TraceId):- DescriptorContinued = exception::tryGetDescriptor(TraceId, continuedFromDll), ErrorDescriptorInformation = exception::tryGetErrorDescriptorInformation(TraceId), ContinueException = ErrorDescriptorInformation:tryGetContinueException(), !, % ясно, что файл не загрузился exception::descriptor (_ErrorCodeContinued, _ClassInfoContinued, _ExceptionContinued, _KindContinued, ExtraInfoContinued, _, _GMTTimeContinued, ExceptionDescriptionContinued, _ThreadIdContinued) = DescriptorContinued, Version = namedValue::mapLookUp(ExtraInfoContinued, version_Parameter, string("")), stdIO::write("Продолженное исключение : ",ExceptionDescriptionContinued,"\nВерсия Dll: ", Version,"\n"), ExtraInfo = ContinueException:getExtraInfo(), ExceptionDescription = ContinueException:getExceptionDescription(), FileName = namedValue::mapLookUp(ExtraInfo,fileSystem_api::fileName_parameter, string("")), Reason = namedValue::mapLookUp(ExtraInfo,common_exception::errorDescription_parameter, string("")), stdIO::write ( "Файл не загружен по причине: ",ExceptionDescription, "\nИмя файла: ", FileName, "\nПричина: ", Reason ), fail. handleFileReadError(TraceId):- isDebugMode = true, % режим отладки !, exceptionDump::dumpToStdOutput(TraceId), fail. handleFileReadError(_TraceId):- fail.
Мы пытаемся здесь найти исключение continuedFromDll и мы вызываем продолжающееся предикатом исключение tryGetContinueException. Это ясно показывает, что, если исключение продолжающееся, то мы можем получить дополнительную информацию касательно самого исключения.
Полностью пример представлен в проекте continueException\testContinuedException\testContinuedException.prj6. Необходимо построить сначала приложение continueException\continueException.prj6 перед тем как вызывать на выполнение исполняемое приложение testContinuedException.
trap vs try
Начиная с версии 7.0 появилась конструкция try/catch/finally, которая заменяет встроенные предикаты trap/3 и finally/2.