There are many facilities in Morphine to let you tailor your environment to your taste and needs. If an existing functionality does not behave exactly as you would like it to do, first check the parameters of the related scenario. You may be able to get the behavior you want by simply setting a parameter appropriately. It is the easiest way to adapt your environment. If there is no parameter which will achieve what you need you may have to add simple commands or to customize existing ones. This Chapter should help you to find your way through the modification and customization facilities.
More details about scenario handling can be found in Chapter 6.
Before displaying the prompt, the system checks whether there is a file called `.morphine-rc' in the current directory and if not, in the user's home directory and if this file is found, Morphine compiles it first. Thus it is possible to put various initialisation commands into this file.
In addition, we recommend that you have Morphine files attached to your applications containing settings dedicated to the applications. These files have to be compiled in Morphine explicitly when needed.
The behavior of many Morphine commands depends on the values of Morphine parameters. Modifying the value of parameters is the easiest way to adapt your environment. There are two types of parameters, ``single'' and ``multiple'' . A parameter of type ``single'' is a global variable, it has exactly one value. A parameter of type ``multiple'' is more like a predicate, it may have several values, or it may not be set at all. Most parameters are of type ``single''.
show_all(parameters,
ScenarioName)
or
show_all(parameters)
will list the existing
parameters. Before you try to set a parameter to a new value
(set_parameter/2
) we recommend that you first get
the current value (get_parameter/2
). Some
parameters have many arguments or convey long lists of values, of which you may
want to tune some only (see the example in the following). You can set a
parameter back to its default value using
set_default/1
. If a parameter is of type
``multiple'' you have to use
unset_parameter/2
to remove a
particular value.
On a tty interface you can either set the parameter interactively with
set_parameter/1
or explicitly using
set_parameter/2
. The latter is useful
to set parameters from within programs or in your
``.morphine-rc'' .
attribute_display
is a ``single'' parameter.
It sets the attributes of a trace event which are displayed by command
print_event
. It
can be set and reset as follows. Initially, the default setting is
used to print the current trace event.
[Morphine]: set_default(attribute_display), print_event. 18: 6 [5] exit partition([1, 2], 3, [1, 2], []) []Then the current value of the
attribute_display
parameter is retrieved.
[Morphine]: get_parameter(attribute_display, L). L = [on, on, on, off, on, off, off, off, on, off, off, on, off, off, on]Assume we want to see what the full trace event looks like. We use
set_parameter/2
to set all the attributes of
a trace event to be displayed.
[Morphine]: set_parameter(attribute_display, [on, on, on, on, on, on, on, on, on, on, on, on, on, on, on]), print_event. 18: 6 [5] exit det (predicate) {qsort} qsort: partition( [1, 2] {list__list(int)}, 3 {int}, [1, 2] {list__list(int)}, [] {list__list(int)})/4-0 []Now, there is too much information in a full trace event to have it displayed at each step. To hide parts of it we use the interactive setting command (
set_parameter/1
)
because we do not know which argument of the parameter corresponds to
which attribute of the event.
[Morphine]: set_parameter(attribute_display). Chrono: [on, off] or abort ? on. Call: [on, off] or abort ? on. Depth: [on, off] or abort ? on. Deter: [on, off] or abort ? off. Port: [on, off] or abort ? on. PredOrFunc: [on, off] or abort ? off. DeclModule: [on, off] or abort ? off. DefModule: [on, off] or abort ? off. Name: [on, off] or abort ? on. Arity: [on, off] or abort ? off. ModeNumber: [on, off] or abort ? off. ListArg: [on, off] or abort ? on. ListNonArgVar: [on, off] or abort ? off. Type: [on, off] or abort ? off. GoalPath: [on, off] or abort ? off. [Morphine]: print_event. 18: 6 [5] exit partition([1, 2], 3, [1, 2], [])
For this particular attribute_display parameter, you can also switch on and
off each event attribute display with toggle/1
. toggle/1
was
straightforwardly implemented with get_parameter/2
and
+set_parameter/2
.
[Morphine]: toggle(args), print_event. 18: 6 [5] exit partition()
Note that any hidden attribute can be retrieved by using
current(<attribute-name> = ...)
. In the following current/1
is used to retrieve the current
chronological event number.
[Morphine]: current(chrono = X). X= 18
arg_undisplay/2
is a
``multiple'' parameter . It specifies
which arguments of which predicates should not be displayed by command
print_event
. Initially the default setting is
used to print the current trace event, all arguments of all predicates
are displayed.
[Morphine]: set_default(arg_undisplay), print_event. 18: 6 [5] exit partition([1, 2], 3, [1, 2], []) []Assume we want to avoid seeing the first argument of predicate
partition/4
. We can use
set_parameter/2
as for ``single''
parameters. Then, when printing a trace event related to
partition/4
an ``ersatz'' is displayed instead
of the first argument. By default the ersatz is ``...'' (see
procedure write_ersatz/0
)
[Morphine]: set_parameter(arg_undisplay, [partition / 4, 1]), print_event. 18: 6 [5] exit partition(..., 3, [1, 2], []) [] yes.Note that you can recover the value of the undisplayed arguments using
current(args = ...)
.
[Morphine]: current(args = [X, _]). X = [3]If you want to set back displayable one of the arguments which are currently set undisplayable, use
unset_parameter/2
.
[Morphine]: unset_parameter(arg_undisplay, [partition/4, 1]), print_event. 18: 6 [5] exit partition([1, 2], 3, [1, 2], []) []
If you want to set back displayable all the arguments which
are currently set undisplayable, use
set_default/1
.
[Morphine]: set_default(arg_undisplay).
You can program new functionalities in Prolog and simply compile them
in the proper module of the Morphine session. For example, let us assume
that we want to be able to graphically display the arguments of the
check_mastermind/4
predicate of the mastermind
program given in Appendix B. Suppose
that we have the Prolog predicate display_mastermind/4
that calls a Tcl/TK script that graphically displays the Mastermind
board of Figure 5 (the code of this predicate is an adaptation of
the Mercury version of display_mastermind/4
which is in
appendix B). We can define ourselves a command
display_mastermind
that will check that we are on the rigth procedure
at the rigth port before calling display_mastermind/4
:
dm :- display_mastermind. % abbreviation for easy use display_mastermind :- % make sure this is the proper procedure current(proc = mastermind_checker:check_mastermind/4-0), % make sure we are on a exit port current(port = exit), % get current argument of check_mastermind/4 current(args = [Guess, Code, Bulls, Cows]), % display it graphically display_mastermind(Code, Guess, Bulls, Cows).
If these definitions are compiled in the Morphine session you can then
use display_mastermind
or dm
on the fly in complement with
the standard display of arguments.
The new functionalities which are simply compiled in the Morphine session
do not have entries in the interface. They are not listed by the
show_all
commands, they appear neither in
the manual, nor in the window-based user interface menus. This
has no importance if the new commands have a limited lifetime.
However, if you think you will need them again or if somebody else is
supposed to use them you should include them in a scenario. The
scenario handler will then provide the interface automatically (see
section 6).
This section will explain only the minimum about the scenario handler to help you customize the existing objects. See section 6 for a precise description of what is generated automatically from the Morphine declarations. In the following we first give some advices. The general mechanism which enables customizations to be done is described. Then we explain how to customize commands and other objects.
Before undertaking any major customization check whether modifying an
existing parameter would solve your problem
(show_all(parameters)
,
show_all(parameters, ScenarioName)
).
If you would like to change a particular sub-behavior of a command or a primitive check whether by chance there exists a procedure which performs this sub-behavior. Customizing this procedure would be easier than customizing the whole command or primitive.
Test your changes on the fly before you change static settings. For
example, if you want to change the display of arguments, first program
a predicate to print them your way; test it on the fly using
current(args = ...)
; only then replace the default
argument display by your predicate.
Try to reuse as much as possible the default implementations. Some of the commands take into account many aspects, which may take you some time to ``rediscover''. If you build on top of them you will benefit from the existing know-how. You will also benefit from updates of the default objects. We will show in the following how you can reuse default implementations.
If you want to add many features and make many changes it might be
time to make
a proper scenario (see
Chapter 6).
If you want to have the default scenario and the modified one both available make the scenario you want to modify local in another module (if possible) and test your customizations there.
Some objects are used everywhere, so consider the side-effects. Unfortunately, there is currently no dedicated Morphine tool which can easily tell you which objects use a given object.
A basic mechanism enables the default implementation of objects to be reused while modifying them. The name of objects and their implementations are two different items linked together automatically by Morphine. In general an Morphine object has (roughly) the following code.
object :- object_impl.Hence customizing
object
can be done by providing a new
implementation reusing (if possible) its default implementation,
such as the following:
[Morphine]: [user]. my_object :- do_something, object_impl, % default implementation do_something_else.Then ask Morphine to replace the default implementation by the customized one using
rebuild_object/5
:
:- rebuild_object(ObjectType, object/0, my_object, Scenario, Module).Command
rebuild_object/5
will roughly generate the following.
object :- my_object_impl.In order to know what is the name of the default implementation of the object you want to customize use
implementation_link/4
. If you
prefer the default implementation of an object you can set it back
using set_default/4
. Examples are given
in the following. Note that at the moment there is no easy way to
change the declaration of an object.
Before customizing a command you need to know its type, the scenario
it belongs to and the module in which this scenario is loaded. This
information is available by typing
man(CommandName)
on line. There are some
subtleties to customize commands of type ``trace'' and ``tool''. Some
examples are given in the following .
Commands of type ``Morphine'' are the most common commands of Morphine and
the simplest . The mechanism described
above can be applied straightforwardly. For example, assume that we
want to customize the print_event/0
command. This is a good example where it is recommended to try to reuse
the default implementation as much as possible. Indeed, it is a
tricky predicate which takes into account many aspects such as Morphine
parameters, operators... We first get some information about
print_event/0
, in particular its type and its related scenario
and module.
[Morphine]: man(print_event). print_event {p} Command which prints the current trace event according to the value of the display parameters. type of command : opium scenario : display (global in Morphine)Now we know
print_event/0
is an ``opium'' command and it belongs
to scenario display
in module opium_kernel_M.
We then retrieve the name of its default implementation.
[Morphine]: implementation_link(command, print_event/0, Impl, 'Morphine'). Impl = print_event_OpAssume we want to print the arguments of the traced goals only for ``call'' and ``exit'' events . We can program a
new_print_event
predicate which checks
the current port. If is is not ``call'' or ``exit'' the ``argument''
attribute is switched off before printing and set back to its previous
value after printing. For printing the default implementation is used.
[Morphine]: [user]. new_print_event :- current(port = Port), ( (Port == call ; Port == exit) -> print_event_Op ; toggle(args), print_event_Op, toggle(args) ).Then we link the new implementation to the command name.
[opium]: rebuild_object(command, print_event/0, new_print_event, display, 'Morphine').Now, when
print_event
is used what is
actually executed is new_print_event
. This is true whether
print_event
is run by a command at toplevel or used by a
debugging program, for example next
will print
the event it retrieves using the customized print_event
.
If after all we prefer the default implementation we can link the command back to its default implementation.
[Morphine]: set_default(command, print_event/0, display, 'Morphine').
For ``trace'' commands , which retrieve and print exactly one trace event, Opium basically generates two clauses
command :- command_np, print_event. command_np :- command_impl.where
command_np
is a primitive which does not print the
event it has moved to. If you implement a variant for
command_impl
, command
rebuild_object/5
will actually link
command_np
to your implementation:
command_np :- my_command_impl.Thus if you want to customize a ``trace'' command using
rebuild_object/5
remember that your implementation should not print the event it has moved to. You also have to pay attention
that your implementation does not call command_np
otherwise it
will loop.
For example, assume you want the next
command to print a
newline before call events. You can customize the existing next/0
by linking it to new_next/0
. In the following we basically
follow the same script as for the ``opium'' command.
[Morphine]: man(next). next {n} Command which moves forward to the next trace event according to the "traced_ports" parameter. type of command : trace scenario : step_by_step-M (global in Morphine) next(N) {n} Command which prints the N next trace events according to the "traced_ports" parameter. N : integer type of command : opium scenario : step_by_step-M (global in Morphine) [Morphine]: implementation_link(command, next/0, Impl, 'Morphine'). Impl = next_Op [Morphine]: [user]. new_next_np :- next_Op, % move to the same place as default next ( current(port = call) -> % if it is a call event opium_nl(trace) % print a newline ; true ). [Morphine]: rebuild_object(command, next/0, new_next_np, 'step_by_step-M', 'Morphine').
Commands of type ``tool'' are like the tools of Eclipse, that is the implementation (tool body) has one more argument than the command to take the actual caller module into account. Hence, your implementation should have the arity of the command + 1.
For example, assume that you would like
show_parameters/1
to remind you that
it shows only the instantiated parameters of the scenario. You can
customize it in the following way.
[Morphine]: man(show_parameters). show_parameters(Scenario) Command which shows the values of all the parameters related to a scenario visible in the current module. Scenario : is_opium_scenario type of command : tool scenario : scenario_handler (global in Morphine) show_parameters Command which shows the values of all the parameters of all scenarios. type of command : opium scenario : scenario_handler (global in Morphine) [Morphine]: implementation_link(command, show_parameters/2, Impl, 'Morphine'). Impl = show_parameters_Op [Morphine]: [user]. my_show_parameters_body(S,M) :- printf("Instantiated parameters of scenario %w:", [S]), show_parameters_Op(S,M). [Morphine]: rebuild_object(command, show_parameters/1, my_show_parameters_body, 'morphine_kernel', 'Morphine').
For example, assume we want to change the way the depth
attribute is displayed by
print_event/0
. We want to replace the
square brackets by `*'. We can customize the
write_attribute/2
procedure as follows.
[Morphine]: man(write_attribute). write_attribute(AttributeName, AttributeValue) Procedure which displays a attribute of the trace event. Where AttributeName is one of {chrono, call, depth, port, module, arity}. To customize the way arguments are displayed you should rather modify write_arg_attribute/3. scenario : display (global in Morphine) [Morphine]: implementation_link(procedure, write_attribute/2, Impl, 'Morphine'). Impl = write_attribute_Op [Morphine]: [user]. new_write_attribute(depth, D) :- % if depth attribute !, write_trace(*), % print a '*' write_trace(D), % print the depth write_trace(*). % print a '*' new_write_attribute(N, V) :- % display other attributes write_attribute_Op(N, V). % in the standard way [Morphine]: rebuild_object(procedure, write_attribute/2,new_write_attribute, display, 'Morphine').