next up previous contents index
Next: Extending the debugging environment Up: Morphine - User Manual Previous: A debugging session with

Subsections

    
Setting up your debugging environment

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.

   
Profiles

All the modifications and the customizations described in the following can be done on the fly but can also be collected in a file which is then compiled in the Morphine session. Hence you can have profile files containing Morphine predicates and directives to set up your environment.

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.

    
Setting parameters

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''  .

Example for a ``single'' parameter.

Parameter 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

Example for a ``multiple'' parameter

Parameter 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).

   
Adding simple commands

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).


  
Figure 5: Board output of display_mastermind/4
\begin{figure}\par\hspace{+0.8 cm}
\psfig{figure=board2.eps,rwidth=12cm,rheight=5cm}
\end{figure}

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).

    
Customizing existing objects

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.

Advice

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.

General mechanism

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.

   
Customizing commands

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 .

Customizing ``opium'' commands

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_Op
Assume 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').

Customizing ``trace'' commands

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').

Customizing ``tool'' commands

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').

     
Customizing primitives, procedures and types

Primitives, procedures and types can be customized in exactly the same way as ``opium'' commands.

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').


next up previous contents index
Next: Extending the debugging environment Up: Morphine - User Manual Previous: A debugging session with
jahier@irisa.fr