Обработка исключений

Материал из wikiru.visual-prolog.com

Исключение - это отклонение программы от маршрута "нормального" выполнения. Пакет exception из состава PFC содержит предикаты для перехвата исключений, которые возникают в программах на Прологе. Это руководство объясняет:

  • Как обрабатывать исключения;
  • Как возбуждать ваши собственные исключения;
  • Как продолжать другое исключение.

Как перехватывать исключения

Рассмотрим программу, которая читает текстовый файл и выводит его содержимое на консоль. PFC предлагает предикат file::readString, который позволяет выполнить эту задачу. Однако, некоторые обстоятельтства могут помешать эту задачу; например, заданное имя файла ссылается на несуществующий файл. Когда предикат file::readString не может выполнить задачу, он генерирует исключение.

Visual Prolog предлагает встроенный предикат trap/3, который перехватывает исключение и позволяет его обработать. Детальное описание предиката trap/3 можно найти в справке по Visual Prolog (Language Reference -> Built-in Domains, Predicates and Constants-> Predicates ->trap).

Соответственно, код для перехвата исключения будет выглядеть так:

trap
  (
  MyTxtFileContent = file::readString(myFileName, _IsUnicode),
  ErrorCode,
  handleFileReadError(ErrorCode)
  ),
  ...

Самое интересное здесь это - имплементация предиката 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.

How to Raise Own Exceptions

Let us consider a program, that checks the size of a specified file. The PFC supply the predicate file::getFileProperties that can return the size of the specified file. If the size is zero, then we can raise an exception by the predicate exception::raise. It is also necessary to create a predicate, which will specify the exception. We have created myExceptionZeroFileSize for this purpose. The code of raising an exception will look like:

clauses
    run():-
        console::init(),
        trap(file::getFileProperties(myFileName, _Attributes, Size, _Creation, _LastAccess, _LastChange),
            ErrorCode,
            handleFileGetPropertiesError(ErrorCode)),
        Size = unsigned64(0, 0), % file size is zero
        !,
        exception::raise(classInfo,myExceptionZeroFileSize,
            [namedValue(fileSystem_api::fileName_parameter, string(myFileName))]).
    run().

The first parameter of the predicate exception::raise is the predicate classInfo, which is generated by the IDE for any new class. It is, of course, a good idea to specify extra information, when an exception is generated. In our case the file name can be helpful, as a zero size belongs to the specified file.

The predicate myExceptionZeroFileSize is created by the template:

clauses
  myExceptionZeroFileSize(classInfo, predicate_Name(),
    "File size cannot be zero").

That is, the first and the second parameters for exception predicates are always classInfo and predicate_Name() respectively, and the third parameter contains a text explanation of the exception reason.

The full example presents in raiseException\raiseException.prj6' project.

How to Continue Another Exception

Let us consider a DLL program that reads a text file and returns its content as a return parameter. If an exception occurs, then the DLL continues it and adds extra information about DLL's version. The code to continue the exception will look like:

constants
    dllVersion = "1.20.0.0".
clauses
    loadFile(FileName) = FileContent :-
        trap(FileContent = file::readString(FileName, _IsUnicode),
          ErrorCode,
          exception::continue(ErrorCode, classInfo, continuedFromDll,
                [namedValue(version_Parameter,string(dllVersion))])).

The predicate continuedFromDll is declared as:

predicates
    continuedFromDll : core::exception as "_ContinuedFromDll@12"

and it is exported from the DLL. The implementation of the continuedFromDll predicate is created by a template ( the same as myExceptionZeroFileSizeabove):

clauses
    continuedFromDll(classInfo, predicate_Name(),
          "Exception continued from continueException.DLL").

When the predicate exception::continue continues the exception, it adds extra information about dllVersion.

The full example presents in continueException\continueException.prj6 project.

Let us look how to catch such continued exception. The code is similar to the code discussed above and we will focus on the differences here.

constants
    myFileName = "my.txt".
clauses
    run():-
        console::init(),
        initExceptionState(exception::getExceptionState()),
        trap(MyTxtFileContent = loadFile(myFileName),
            ErrorCode,
            handleFileReadError(ErrorCode)),
        !,
        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(),
        !, %file cannot be loaded
        exception::descriptor(_ErrorCodeContinued,
            _ClassInfoContinued,
            _ExceptionContinued,
            _KindContinued,
            ExtraInfoContinued,
            _,
            _GMTTimeContinued,
            ExceptionDescriptionContinued,
            _ThreadIdContinued) = DescriptorContinued,
        Version = namedValue::mapLookUp(ExtraInfoContinued, version_Parameter, string("")),
        stdIO::write("Exception continued : ",ExceptionDescriptionContinued,
            "\nDllVersion: ", 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("Cannot load file due to: ",ExceptionDescription,
            "\nFile Name: ", FileName,
            "\nReason: ", Reason ),
        fail.
    handleFileReadError(TraceId):-
        isDebugMode = true,
        !,
        exceptionDump::dumpToStdOutput(TraceId),
        fail.
    handleFileReadError(_TraceId):-
        fail.

In the code we try to find continuedFromDll exception and we retrieve the continued exception by the predicate tryGetContinueException. This clearly shows that if an exception was continued, then we can gain more information about the exception.

The full example presents in continueException\testContinuedException\testContinuedException.prj6 project. It is necessary to build continueException\continueException.prj6 before running testContunedException executable.

Ссылки