Did you know ... | Search Documentation: |
Pack logtalk -- logtalk-3.86.0/manuals/_sources/userman/printing.rst.txt |
.. This file is part of Logtalk https://logtalk.org/ SPDX-FileCopyrightText: 1998-2024 Paulo Moura <pmoura@logtalk.org> SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
.. _printing_printing:
Applications, components, and libraries often print all sorts of messages. These include banners, logging, debugging, and computation results messages. But also, in some cases, user interaction messages. However, the authors of applications, components, and libraries often cannot anticipate the context where their software will be used and thus decide which and when messages should be displayed, suppressed, or diverted. Consider the different components in a Logtalk application development and deployment. At the base level, you have the Logtalk compiler and runtime. The compiler writes messages related to e.g. compiling and loading files, compiling entities, and compilation warnings and errors. The runtime may write banner messages or throw execution errors that may result in printing human-level messages. The development environment can be console-based, or you may be using a GUI tool such as PDT. In the latter case, PDT needs to intercept the Logtalk compiler and runtime messages to present the relevant information using its GUI. Then you have all the other components in a typical application. For example, your own libraries and third-party libraries. The libraries may want to print messages on their own, e.g. banners, debugging information, or logging information. As you assemble all your application components, you want to have the final word on which messages are printed, where, and when. Uncontrolled message printing by libraries could potentially disrupt application flow, expose implementation details, spam the user with irrelevant details, or break user interfaces.
The solution is to decouple the calls to print a message from the actual printing of the output text. The same is true for calls to read user input. By decoupling the call to input some data from the actual read of the data, we can easily switch from, for example, a command-line interface to a GUI input dialog or even automate providing the data (e.g., when automating testing of the user interaction).
Logtalk provides a solution based on the structured message printing mechanism that was introduced by Quintus Prolog, where it was apparently implemented by Dave Bowen (thanks to Richard O'Keefe for the historical bits). This mechanism gives the programmer full control of message printing, allowing it to filter, rewrite, or redirect any message. Variations of this mechanism can also be found in some Prolog systems, including SICStus Prolog, SWI-Prolog, and YAP. Based on this mechanism, Logtalk introduces an extension that also allows abstracting asking a user for input. Both mechanisms are implemented by the :ref:`logtalk apis:logtalk/0` built-in object and described in this section. The message printing mechanism is extensively used by the Logtalk compiler itself and by the developer tools. The question-asking mechanism is used e.g. in the :doc:`../devtools/debugger` tool.
.. _printing_messages:
The main predicate for printing a message is :ref:`logtalk::print_message/3 <methods_print_message_3>`. A simple example, using the Logtalk runtime, is:
.. code-block:: text
| ?- logtalk::print_message(banner, core, banner)
.
Logtalk 3.23.0 Copyright (c) 1998-2018 Paulo Moura yes
The first argument of the predicate is the kind of message that we
want to print. In this case, we use banner
to indicate that
we are printing a product name and copyright banner. An extensive
list of message kinds is supported by default:
banner
banner messages (used e.g. when loading tools or main application
components; can be suppressed by setting the :ref:`report <flag_report>`
flag to warnings
or off
)
help
messages printed in reply to the user asking for help (mostly for
helping port existing Prolog code)
information
and information(Group)
messages usually printed in reply to a user's request for information
silent
and silent(Group)
not printed by default (but can be intercepted using the
message_hook/4 predicate)
comment
and comment(Group)
useful but usually not essential messages (can be suppressed by
setting the :ref:`report <flag_report>` flag to warnings
or off
)
warning
and warning(Group)
warning messages (generated e.g. by the compiler; can be suppressed
by turning off the :ref:`report <flag_report>` flag)
error
and error(Group)
error messages (generated e.g. by the compiler)
debug, debug(Group)
debugging messages (by default, only printed when the :ref:`debug <flag_debug>`
flag is turned on; the print_message/3 goals for these messages are
suppressed by the compiler when the :ref:`optimize <flag_optimize>` flag is
turned on)
question, question(Group)
questions to a user
Using a compound term allows easy partitioning of messages of the same kind in different groups. Note that you can define your own alternative message kind identifiers for your own components, together with suitable definitions for their associated prefixes and output streams.
The second argument of print_message/3 represents the component
defining the message being printed. In this context, component is a generic
term that can designate, e.g., a tool, a library, or some sub-system in a large
application. In our example, the component name is core
, identifying the
Logtalk compiler/runtime. This argument was introduced to provide multiple
namespaces for message terms and thus simplify programming-in-the-large by
allowing easy filtering of all messages from a specific component and also
avoiding conflicts when two components happen to define the same message term
(e.g., banner
). Users should choose and use a unique name for a component,
which usually is the name of the component itself. For example, all messages
from the :doc:`../devtools/lgtunit` tool use lgtunit
for the component
argument. The compiler and runtime are interpreted as a single component
designated as core
.
The third argument of print_message/3 is the message itself, represented
by a term. In the above example, the message term is banner
. Using a
term to represent a message instead of a string with the message text itself
has significant advantages. Notably, it allows using a compound term for
easy parameterization of the message text and simplifies machine processing,
localization of applications, and message interception. For example:
.. code-block:: text
| ?- logtalk::print_message(comment, core, redefining_entity(object, foo))
.
% Redefining object foo yes
The use of message terms requires a solution for generating the actual text of the messages. This is supported by defining grammar rules for the :ref:`logtalk::message_tokens//2 <methods_message_tokens_2>` multifile non-terminal, which translates a message term, for a given component, to a list of tokens. For example:
::
:- multifile(logtalk::message_tokens//2). :- dynamic(logtalk::message_tokens//2).
logtalk::message_tokens(redefining_entity(Type, Entity), core)
-->
['Redefining ~w ~q'-[Type, Entity], nl].
The following tokens can be used when translating a message:
at_same_line
Signals a following part to a multi-part message with no line break
in between; this token is ignored when it's not the first in the list
of tokens
tab(Expression)
Evaluate the argument as an arithmetic expression and write the resulting
number of spaces; this token is ignored when the number of spaces is not
positive
nl
Change line in the output stream
flush
Flush the output stream (by calling the flush_output/1 standard
predicate)
Format-Arguments
Format
must be an atom and Arguments
must be a list of format
arguments (the token arguments are passed to a call to the
format/3 de facto standard predicate)
term(Term, Options)
Term
can be any term and Options
must be a list of valid
write_term/3 output options (the token arguments are passed to a
call to the write_term/3 standard predicate)
ansi(Attributes, Format, Arguments)
Taken from SWI-Prolog; by default, do nothing; can be used for styled
output
begin(Kind, Var)
Taken from SWI-Prolog; by default, do nothing; can be used together
with end(Var)
to wrap a sequence of message tokens
end(Var)
Taken from SWI-Prolog; by default, do nothing
The logtalk
object also defines public predicates for printing a list
of tokens, for hooking into printing an individual token, and for setting
default output streams and message prefixes. For example, the SWI-Prolog
adapter file uses the print message token hook predicate to enable coloring
of messages printed on a console.
Defining tokenization rules for every message is not always necessary, however.
Logtalk defines several meta-messages that are handy for simple cases and
temporary messages only used during application development, notably debugging
messages. See the :ref:debugging_messages
section and the
:ref:`logtalk built-in object apis:logtalk/0` remarks section for details.
The :ref:`logtalk::message_prefix_stream/4 <methods_message_prefix_stream_4>` hook predicate can be used to define a message line prefix and an output stream for printing messages of a given kind and component. For example:
::
:- multifile(logtalk::message_prefix_stream/4). :- dynamic(logtalk::message_prefix_stream/4). logtalk::message_prefix_stream(comment, my_app, '% ', user_output). logtalk::message_prefix_stream(warning, my_app, '* ', user_error).
A single clause at most is expected per message kind and component pair. When this predicate is not defined for a given kind and component pair, the following defaults are used:
::
kind_prefix_stream(banner, '', user_output). kind_prefix_stream(help, '', user_output). kind_prefix_stream(question, '', user_output). kind_prefix_stream(question(_), '', user_output). kind_prefix_stream(information, '% ', user_output). kind_prefix_stream(information(_), '% ', user_output). kind_prefix_stream(comment, '% ', user_output). kind_prefix_stream(comment(_), '% ', user_output). kind_prefix_stream(warning, '* ', user_error). kind_prefix_stream(warning(_), '* ', user_error). kind_prefix_stream(error, '! ', user_error). kind_prefix_stream(error(_), '! ', user_error). kind_prefix_stream(debug, '>>> ', user_error). kind_prefix_stream(debug(_), '>>> ', user_error).
When the message kind is unknown, information
is used instead.
Some applications require copying and saving messages without diverting them from their default stream. For simple cases, this can be accomplished by intercepting the messages using the :ref:`logtalk::message_hook/4 <methods_message_hook_4>` multifile hook predicate (see next section). In more complex cases, where messages are already intercepted for a different purpose, it can be tricky to use multiple definitions of the message_hook/4 predicate as the order of the clauses of a multiple predicate cannot be assumed in general (for all message_hook/4 predicate definitions to run, all but the last one to be called must fail). Using a single master definition is also not ideal as it would result in strong coupling instead of a clean separation of concerns.
The experimental :ref:`logtalk::message_prefix_file/6 <methods_message_prefix_file_6>` hook predicate can be used to define a message line prefix and an output file for copying messages of a given kind and component pair. For example:
::
:- multifile(logtalk::message_prefix_file/6). :- dynamic(logtalk::message_prefix_file/6).
logtalk::message_prefix_file(error, app, '! ', 'log.txt', append, [])
.
logtalk::message_prefix_file(warning, app, '! ', 'log.txt', append, [])
.
A single clause at most is expected per message kind and component pair.
This predicate is called by default by the message printing mechanism.
Definitions of the message_hook/4 hook predicate are free to decide
if the logtalk::message_prefix_file/6
predicate should be called and
acted upon.
Calls to the :ref:`logtalk::print_message/3 <methods_print_message_3>` predicate can be intercepted by defining clauses for the :ref:`logtalk::message_hook/4 <methods_message_hook_4>` multifile hook predicate. This predicate can suppress, rewrite, and divert messages.
As a first example, assume that you want to make Logtalk startup less verbose by suppressing printing of the default compiler flag values. This can be easily accomplished by defining the following category in a settings file:
::
:- category(my_terse_logtalk_startup_settings)
.
:- multifile(logtalk::message_hook/4). :- dynamic(logtalk::message_hook/4). logtalk::message_hook(default_flags, comment(settings), core, _).
:- end_category.
The printing message mechanism automatically calls the message_hook/4 hook predicate. When this call succeeds, the mechanism assumes that the message has been successfully handled.
As another example, assume that you want to print all otherwise silent compiler messages:
::
:- category(my_verbose_logtalk_message_settings)
.
:- multifile(logtalk::message_hook/4). :- dynamic(logtalk::message_hook/4). logtalk::message_hook(_Message, silent, core, Tokens) :- logtalk::message_prefix_stream(comment, core, Prefix, Stream), logtalk::print_message_tokens(Stream, Prefix, Tokens). logtalk::message_hook(_Message, silent(Key), core, Tokens) :- logtalk::message_prefix_stream(comment(Key), core, Prefix, Stream), logtalk::print_message_tokens(Stream, Prefix, Tokens).
:- end_category.
.. _printing_questions:
Logtalk structured question-asking mechanism complements the message printing mechanism. It provides an abstraction for the common task of asking a user a question and reading back its reply. By default, this mechanism writes the question, writes a prompt, and reads the answer using the current user input and output streams but allows all steps to be intercepted, filtered, rewritten, and redirected. Two typical examples are using a GUI dialog for asking questions and automatically providing answers to specific questions.
The question-asking mechanism works in tandem with the message printing
mechanism, using it to print the question text and a prompt. It provides
an asking predicate and a hook predicate, both declared and defined in
the logtalk
built-in object. The asking predicate,
:ref:`logtalk::ask_question/5 <methods_ask_question_5>`,
is used for asking a question and reading the answer. Assume that we
defined the following message tokenization and question prompt and
stream:
::
:- category(hitchhikers_guide_to_the_galaxy)
.
:- multifile(logtalk::message_tokens//2). :- dynamic(logtalk::message_tokens//2). % abstract the question text using the atom ultimate_question; % the second argument, hitchhikers, is the application component logtalk::message_tokens(ultimate_question, hitchhikers) --> ['The answer to the ultimate question of life, the universe and everything is?'-[], nl].
:- multifile(logtalk::question_prompt_stream/4). :- dynamic(logtalk::question_prompt_stream/4).
% the prompt is specified here instead of being part of the question text
% as it will be repeated if the answer doesn't satisfy the question closure
logtalk::question_prompt_stream(question, hitchhikers, '> ', user_input)
.
:- end_category.
After compiling and loading this category, we can now ask the ultimate question:
.. code-block:: text
| ?- logtalk::ask_question(question, hitchhikers, ultimate_question, '=='(42), N)
.
The answer to the ultimate question of life, the universe and everything is? > 42.
N = 42 yes
Note that the fourth argument, '=='(42)
in our example, is a :term:closure
that is used to check the answers provided by the user. The question is repeated
until the goal constructed by extending the closure with the user answer
succeeds. For example:
.. code-block:: text
| ?- logtalk::ask_question(question, hitchhikers, ultimate_question, '=='(42), N)
.
The answer to the ultimate question of life, the universe and everything is?
> icecream.
> tea.
> 42.
N = 42 yes
Practical usage examples of this mechanism can be found, e.g., in the
debugger
tool where it's used to abstract the user interaction when
tracing a goal execution in debug mode.
Calls to the :ref:`logtalk::ask_question/5 <methods_ask_question_5>` predicate can be intercepted by defining clauses for the :ref:`logtalk::question_hook/6 <methods_question_hook_6>` multifile hook predicate. This predicate can suppress, rewrite, and divert questions. For example, assume that we want to automate testing and thus cannot rely on someone manually providing answers:
::
:- category(hitchhikers_fixed_answers)
.
:- multifile(logtalk::question_hook/6). :- dynamic(logtalk::question_hook/6). logtalk::question_hook(ultimate_question, question, hitchhikers, _, _, 42).
:- end_category.
After compiling and loading this category, trying the question again will now skip asking the user:
.. code-block:: text
| ?- logtalk::ask_question(question, hitchhikers, ultimate_question, '=='(42), N)
.
N = 42 yes
In a practical case, the fixed answer would be used for follow-up goals
being tested. The question-answer read loop (which calls the question
check closure) is not used when a fixed answer is provided using the
logtalk::question_hook/6
predicate thus preventing the creation
of endless loops. For example, the following query succeeds:
.. code-block:: text
| ?- logtalk::ask_question(question, hitchhikers, ultimate_question, '=='(41), N)
.
N = 42 yes
Note that the logtalk::question_hook/6
predicate takes as argument
the closure specified in the logtalk::ask_question/5
call, allowing
a fixed answer to be checked before being returned.