Files
mercury/extras/morphine/source/collect.op
Zoltan Somogyi 056d2213af Avoid using some Mercury keywords.
browser/browser_info.m:
    Avoid using "output" (and "input", for the sake of symmetry)
    as function symbols.

browser/debugger_interface.m:
    Avoid using "pred" and "func" as function symbols by putting a prefix
    before each function symbol in the affected type.

browser/dl.m:
    Avoid using "local" (and "global", again for symbols) as function symbols.

profiler/output_prof_info.m:
    Avoid using "output" as a type name and as a function symbol.

browser/browse.m:
browser/collect_lib.m:
browser/declarative_user.m:
browser/interactive_query.m:
profiler/generate_output.m:
profiler/output.m:
    Conform to the changes above.

extras/morphine/source/browse.op:
extras/morphine/source/collect.op:
extras/morphine/source/current_arg.op:
extras/morphine/source/current_slots.op:
extras/morphine/source/exec_control.op:
extras/morphine/source/forward_move.op:
extras/morphine/source/interactive_queries.op:
    Conform to the renames of the function symbols in debugger_interface.m.

    Since this code is in Prolog, I cannot be sure that I changed all the
    places that should be changed, but that does not matter much.

    Since Morphine was designed to work with the Prolog dialects of 1999,
    had its last update in 2002, and we never test it, it is very likely
    that it hasn't worked in a long time. We keep it around because
    (a) it may interest someone, and (b) it doesn't require significant
    maintenance. The fact that it does not run may be regrettable, but
    it is not actually regretted by many would-be users, or (even) any at all.

    (I actually noticed and fixed a bug while doing the above change:
    it was a typo in a function symbol name.)
2016-05-13 09:07:58 +10:00

590 lines
18 KiB
Plaintext

%------------------------------------------------------------------------------%
% Copyright (C) 1999-2001 INRIA/INSA de Rennes/IFSIC.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file License in the Morphine distribution.
%
% Author : Erwan Jahier
% File : collect.op
%
% This file implements the collect command.
%
% There are several things to do in order to be able to execute a
% `collect/1' command:
% 1) create a file that will that contain the definition of collected_type,
% `initialize/1' and `filter/4',
% 2) generate `collect.m' from this file (`generate_collect/1'),
% 3) compile `collect.m' (`compile_collect/0'),
% 4) dynamically link it with the current execution (`dyn_link_collect/2').
% 5) run the command (`run_command/1').
opium_scenario(
name : collect,
files : [collect],
scenarios : [],
message :
"Scenario that implements the `collect/2' monitoring command that collects \
runtime information from Mercury program executions. It is intended to let \
users easily implement their own monitors with acceptable performances.\n\
\n\
To use it, users just need to define 4 things in a file, using the Mercury \
syntax.\n\
(1) `accumulator_type' which is the type of the accumulator. \n\
(2) A predicate `initialize/1' which initializes this collecting \n\
variable. Predicate `initialize/1' should respect the \n\
following declarations:\n\
:- pred initialize(accumulator_type).\n\
:- mode initialize(out) is det.\n\
(3) A predicate `filter/4' which updates the collecting variable at \n\
each execution event. The fourth argument of `filter/4' \n\
indicates whether to stop collecting. If this variable is set to \n\
`stop', the collect process stops; if it is set to `continue', it \n\
continues. If this variable is always set to `continue', the \n\
collecting processes until the last event is reached. `filter/4' \n\
should respect the following declarations:\n\
:- pred filter(event, accumulator_type, accumulator_type, \n\
stop_or_continue).\n\
:- mode filter(in, acc_in, acc_out, out) is det.\n\
where modes `acc\_in' and `acc\_out' are equivalent to\n\
`in' and `out' respectively by default.\n\
(4) Optionally, a `post_process/2' function that lets one post-process\n\
the final value of the accumulator. `post_process/2' should respect\n\
the following declarations:\n\
:- pred post_process(accumulator_type, collected_type).\n\
:- mode post_process(in, out) is det.\n\
If `collected_type' is different from the `accumulator_type', \n\
`collected_type' should also be defined; otherwise by default,\n\
`collected_type' is automatically defined as being the same type\n\
as `accumulator_type'.\n\
(5) And optionally, a mode definition for `acc_in' and `acc_out'\n\
if one wants to override their default values.\n\
\n\
Then, this file is used to generate the Mercury module `collect.m', \
which is compiled and dynamically linked with the current execution. \
When a `collect' request is made from the external debugger, a variable \
of type `accumulator_type' is first initialized (with `initialize/1') and \
then updated (with `filter/4') for all the remaining events of the \
execution. When the fourth argument of `filter/4' is equal to `stop', or when \
the end of the execution is reached, the last value of \
the collecting variable is send to Morphine.\n\
\n\
The event type is defined as follows (for more details about the meaning of \
each event attributes, please refer to the morphine Reference Manual):\n\
\n\
:- type event ---> \n\
event(\n\
event_number,\n\
call_number,\n\
depth_number,\n\
trace_port_type,\n\
pred_or_func,\n\
declarated_module_name,\n\
defined_module_name, \n\
proc_name,\n\
arity,\n\
mode_number,\n\
determinism,\n\
goal_path_string,\n\
line_number).\n\
\n\
:- type event_number == int.\n\
:- type call_number == int.\n\
:- type depth_number == int.\n\
:- type trace_port_type\n\
---> call\n\
; exit\n\
; redo\n\
; fail\n\
; ite_cond\n\
; ite_then\n\
; ite_else\n\
; neg_enter\n\
; neg_success\n\
; neg_failure\n\
; disj\n\
; switch\n\
; nondet_pragma_first\n\
; nondet_pragma_later\n\
; exception.\n\
:- type pred_or_func\n\
---> predicate\n\
; function.\n\
:- type declarated_module_name == string.\n\
:- type defined_module_name == string.\n\
:- type proc_name == string.\n\
:- type arity == int.\n\
:- type mode_number == int.\n\
:- type determinism == int. \n\
:- type goal_path_string == string.\n\
:- type line_number == int.\n\
:- type procedure ---> proc(\n\
pred_or_func, \n\
declarated_module_name, \n\
proc_name, \n\
arity, \n\
mode_number).\n\
:- type arguments == list(univ).\n\
\n\
Here are functions that eases the access to event attributes:\n\
\n\
:- func chrono(event::in) = (event_number::out) is det.\n\
:- func call(event::in) = (call_number::out) is det.\n\
:- func depth(event::in) = (depth_number::out) is det.\n\
:- func port(event::in) = (trace_port_type::out) is det.\n\
:- func proc_type(event::in) = (pred_or_func::out) is det.\n\
:- func decl_module(event::in) = (declarated_module_name::out) is det.\n\
:- func def_module(event::in) = (defined_module_name::out) is det.\n\
:- func proc_name(event::in) = (proc_name::out) is det.\n\
:- func proc_arity(event::in) = (arity::out) is det.\n\
:- func proc_mode_number(event::in) = (mode_number::out) is det.\n\
:- func proc(event::in) = (procedure::out) is det.\n\
:- func determinism(event::in) = (determinism::out) is det.\n\
:- func goal_path(event::in) = (goal_path_string::out) is det.\n\
:- func line_number(event::in) = (line_number::out) is det.\n\
:- func arguments(event::in) = (arguments::out) is det. (*)\n\
\n\
(*) To be able to retrieve arguments, you to need to have the opium parameter \n\
`collect_arg' set to yes (`man collect_arg.' for more details).\n\
\n\
Predicate `collect/2' can be seen as a `foldl/4' operator except that \
it does not take a list as argument but operates on the fly on a list \
of events; and we can stop the process at anytime thanks the fourth \
argument of `filter/4'."
).
%------------------------------------------------------------------------------%
opium_command(
name : collect,
arg_list : [File, Result],
arg_type_list : [is_atom_or_string, is_atom_or_var],
abbrev : _,
interface : button,
command_type : opium,
implementation : collect_Op,
parameters : [collect_arg],
message :
"If File contains the implementation of the Mercury predicates \
`initialize/1' and `filter/4', `collect(File, Result)' calls `filter/4' \
with each remaining event of the current execution and an accumulator \
initialized by `initialize/1', and returns the final value in `Result'. \
The fourth argument of filter is a flag that is set to `continue' or \
`stop' depending if you want to continue or stop the monitoring \
process; this useful if one wants to be able to stop the monitoring \
process before the last event is reached.\n\
\n\
Here is an example of a simple monitor that counts calls.\n\
If a file `count_call' contains the following statements:\n\
`\n\
:- import_module int.\n\
:- type accumulator_type == int.\n\
\n\
initialize(0).\n\
\n\
filter(Event, AccIn, AccOut, continue) :-\n\
( port(Event) = call ->\n\
AccOut = AccIn + 1\n\
;\n\
AccOut = AccIn\n\
).\n\
'\n\
Then the goal `run(queens), collect(count_call, Result)' will unify `Result' \
with the number of calls occurring during the execution of `queens' program.\
You can also post-process the last value of the accumalator by defining \
post_process(accumulator_type::in, collected_type::out) predicate. If collected_type \
is different from accumulator_type, you should also define it. For example, \
you can post-process the final value of the `count_call' monitor by adding \
the following statements to `count_call' file: \n\
`\n\
:- type collected_type == string.\n\
post_process(Int, String) :-\n\
if Int > 100 then \n\
String = \"More than 100 calls have been done\"\n\
else\n\
String = \"Less than 101 calls have been done\"\n\
'\n\
"
).
collect_Op(File, Result) :-
check_a_program_is_running("collect/2"),
(
% File might be an atom or a string.
string(File)
->
File = FileStr
;
atom_string(File, FileStr)
),
append_strings(FileStr, ".so", File_so),
(
% We don't generate again collect.so if the collect input
% file has already been collected and if it has not been
% modified.
get_file_info(File, mtime, Time),
get_file_info(File_so, mtime, Time_so),
Time < Time_so,
concat_string(["cp ", File_so, " collect.so"], Cmd1),
sh(Cmd1),
!
;
generate_collect(File),
compile_collect,
concat_string(["cp collect.so ", File_so], Cmd2),
sh(Cmd2)
),
dyn_link_collect(FileStr, File_so),
run_collect(Result).
%------------------------------------------------------------------------------%
opium_parameter(
name : collect_arg,
arg_list : [YesOrNo],
arg_type_list : [member([yes, no])],
parameter_type : single,
default : [no],
commands : [collect],
message :
"Specifies whether or not it is possible to use `arguments/1' within \
`collect:filter/4'. If you do not use arguments, it is better to set this \
parameter to `no' since when arguments are very big, it might slow down \
the execution of collect a lot."
).
%------------------------------------------------------------------------------%
opium_primitive(
name : compile_collect,
arg_list : [],
arg_type_list : [],
abbrev : _,
implementation : compile_collect_Op,
message :
"Compiles the module `collect.m'."
).
compile_collect_Op :-
write("Compiling collect.m...\n"),
sh("rm -f collect.so collect.o"),
current_grade(Grade),
concat_string([
"mmc --no-warn-det-decls-too-lax --grade ",
Grade,
" -O6",
" -c collect.m"], Command1),
print(Command1), nl,
sh(Command1),
exists("collect.o"),
concat_string([
"ml --grade ",
Grade,
" --make-shared-lib ",
"-o collect.so collect.o"], Command2),
print(Command2), nl,
sh(Command2),
exists("collect.so"),
!,
morphine_write_debug("collect.m has been compiled successfully.\n").
compile_collect_Op :-
write("\n\n***** Compilation of module collect failed.\n"),
abort.
%------------------------------------------------------------------------------%
opium_primitive(
name : current_grade,
arg_list : [Grade],
arg_type_list : [var],
abbrev : _,
implementation : current_grade_Op,
message :
"Retrieves the grade the current program execution has been compiled with."
).
current_grade_Op(Grade) :-
check_a_program_is_running("current_grade/1"),
send_message_to_socket(current_grade),
read_message_from_socket(response_grade(Grade)).
%------------------------------------------------------------------------------%
opium_primitive(
name : generate_collect,
arg_list : [File],
arg_type_list : [is_atom_or_var],
abbrev : _,
implementation : generate_collect_Op,
message :
"Generates a Mercury module named `collect.m' from file `File'; `File' should \
contain the definition of `collected_type', `initialize/1', and `filter/4'."
).
generate_collect_Op(File) :-
sh("rm -f collect.m"),
open("collect.m", write, collect),
getenv("MERCURY_MORPHINE_DIR", MorphineDir),
append_strings(MorphineDir, "/source/collect.in", CollectIn),
open(CollectIn, read, collect_in),
open(File, read, collect_body),
read_string(collect_in, "", _, In),
read_string(collect_body, "", _, Body),
write(collect, In),
write(collect, ""),
write(collect, "\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n"),
(
is_collected_type_defined(File)
;
% Add a definition for collected_type if necessary
write(collect, ":- type collected_type == accumulator_type.\n")
),
(
is_post_process_pred_defined(File)
;
% Add a definition for post_process/2 if necessary
write(collect, "post_process(Acc, Acc).\n")
),
(
is_there_any_mode_declaration(File)
;
% Add a mode definition of `acc_in' and `acc_out' if not
% present in File.
write(collect, ":- mode acc_in :: in.\n"),
write(collect, ":- mode acc_out :: out.\n\n")
),
% From the Mercury Reference manual:
% The `source_file' pragma and `#line' directives provide
% support for preprocessors and other tools that generate
% Mercury code. The tool can insert these directives into the
% generated Mercury code to allow the Mercury compiler to
% report diagnostics (error and warning messages) at the
% original source code location, rather than at the location in
% the automatically generated Mercury code.
printf(collect, ":- pragma source_file(\"%w\").\n", [File]),
write(collect, "#1\n"),
write(collect, Body),
close(collect),
close(collect_in).
% Check if accumulator_type is defined.
is_collected_type_defined(File) :-
open(File, read, S),
(
is_collected_type_defined_do(S),
close(S),
!
;
close(S),
fail
).
is_collected_type_defined_do(S) :-
read_mercury_term(S, Term),
term_string(Term, String),
(
append_strings(":- type(--->(collected_type", _, String),
!
;
append_strings(":- type(collected_type", _, String),
!
;
Term = end_of_file,
!,
fail
;
is_collected_type_defined_do(S)
).
% Check if post_process is defined.
is_post_process_pred_defined(File) :-
open(File, read, S),
(
is_post_process_pred_defined_do(S),
close(S),
!
;
close(S),
fail
).
is_post_process_pred_defined_do(S) :-
read_mercury_term(S, Term),
(
term_string(Term, String),
append_strings("post_process", _, String)
;
Term = end_of_file,
!,
fail
;
is_post_process_pred_defined_do(S)
).
% Check if there is a mode definition of `acc_in' and `acc_out' in the
% file `File'. Those are to let users specify di and uo as modes for filter.
is_there_any_mode_declaration(File) :-
open(File, read, S),
(
is_there_any_mode_declaration_do(S),
close(S),
!
;
close(S),
fail
).
is_there_any_mode_declaration_do(S) :-
read_mercury_term(S, Term),
(
Term =.. [':-', ModeDecl|_],
term_string(ModeDecl, ModeDeclStr),
(
substring(ModeDeclStr, "mode ::(acc_in",_),
!
;
substring(ModeDeclStr, "mode ::(acc_out",_)
)
;
Term = end_of_file,
!,
fail
;
is_there_any_mode_declaration_do(S)
).
%------------------------------------------------------------------------------%
opium_primitive(
name : dyn_link_collect,
arg_list : [File, FileSo],
arg_type_list : [is_string, is_string],
abbrev : _,
implementation : dyn_link_collect_Op,
message :
"Dynamically links the collect module with the current execution."
).
dyn_link_collect_Op(File, FileSo) :-
check_a_program_is_running("dyn_link_collect/2"),
(
exists("collect.so"),
!
;
exists("collect.m"),
compile_collect,
exists("collect.so"),
!
;
write("Can't find `collect.m'; you should "),
write("use `generate_collect/1' primitive before.\n"),
fail
),
send_message_to_socket(link_collect("\"./collect.so\"")),
read_message_from_socket(Result),
( Result = response_link_collect_succeeded ->
morphine_write_debug("collect.so has been linked successfully.\n")
;
% if the Mercury program has been compiled in another grade, the
% linking will fail here. So if it fails, we recompile the collect
% module and try again.
touch(File), % to make sure it really recompiles it
print("Because the dynamic linking failed for some reasons, "),
print("we recompile the collect module and retry.\n"),
generate_collect(File),
compile_collect,
concat_string(["cp collect.so ", FileSo], Cmd3),
sh(Cmd3),
send_message_to_socket(link_collect("\"./collect.so\"")),
read_message_from_socket(Result2),
( Result2 = response_link_collect_succeeded ->
morphine_write_debug("collect.so has been linked "),
morphine_write_debug("successfully this time.\n")
;
print("**** collect.so has not been linked.\n"),
abort
)
).
touch(File) :-
append_strings("touch ", File, Cmd),
morphine_write_debug(Cmd),
sh(Cmd).
%------------------------------------------------------------------------------%
opium_primitive(
name : run_collect,
arg_list : [Result],
arg_type_list : [var],
abbrev : _,
implementation : run_collect_Op,
message :
"Executes the collect command provided that `collect.m' has been correctly \
generated, compiled, and dynamically linked with the current execution."
).
run_collect_Op(Result) :-
check_a_program_is_running("run_collect/1"),
(
collect_arg(yes),
send_message_to_socket(collect_arg_on),
read_message_from_socket(response_collect_arg_on_ok),
!
;
collect_arg(no),
send_message_to_socket(collect_arg_off),
read_message_from_socket(response_collect_arg_off_ok),
!
;
print("Error in run_collect_Op.\n"),
abort
),
send_message_to_socket(collect),
read_message_from_socket(CollectLinked),
(
CollectLinked == response_collect_linked,
read_message_from_socket(Msg),
(
Msg = response_collected(Result),
read_message_from_socket(IsExecutionContinuing),
(
IsExecutionContinuing = execution_continuing,
!
;
IsExecutionContinuing = execution_terminated,
end_connection
),
!
;
print("unexpected message from the Mercury "),
printf("process: %w\n", [Result]),
end_connection,
abort
),
!
;
CollectLinked == response_collect_not_linked,
print("You can't call `run_collect/1'; "),
print("The collect module has not been linked with "),
print("the current execution (cf `dyn_link_collect/2').\n"),
!,
fail
;
write("unexpected message from the Mercury "),
printf("process: %w\n", [CollectLinked]),
end_connection,
abort
).
check_a_program_is_running(CommandStr) :-
(
getval(state_of_morphine, State),
State = running,
!
;
printf("You can't call %w; no program is running.\n",
[CommandStr]),
fail
).