Files
mercury/mdbcomp/mdbcomp.goal_path.m
2025-11-07 10:46:09 +11:00

783 lines
29 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2011-2012 The University of Melbourne.
% Copyright (C) 2014-2015, 2017-2018, 2022-2025 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%---------------------------------------------------------------------------%
%
% File: goal_path.m
% Authors: zs, pbone
%
% We can think of the Mercury goal that defines a procedure as a tree, where
%
% - the leaves are primitive goals, and
% - the interior nodes are compound goals.
%
% The goal_id, forward_goal_path and reverse_goal_path types that this module
% defines all describe the position of a goal in this tree. Therefore values
% of these three types can uniquely identify a goal within its defining
% procedure.
%
% Goal ids are allocated in a depth-first manner that guarantees the following
% invariants:
%
% - the goal id of a goal representing the procedure body will be 0, and
% - the goal id of a goal will be greater than the goal ids of all the goals
% that contain it.
%
% A value of the goal_path_step type says which branch to take at an
% interior node, such as which conjunct of a conjunction we want to select.
%
% A forward goal path lists the steps from the root of the tree to the goal
% being identified. (These are the kinds of goal paths you can see
% on mdb events, for example.)
%
% A reverse goal path lists the steps from to the goal being identified
% back to the root of the tree.
%
% HISTORY
%
% Of these three types, the first one that the compiler used is forward goal
% paths. It used these for purposes such as identifying which subgoal
% of a procedure body an mdb event occurred at. However, the code constructing
% these forward goal paths to put into the static data structures used by mdb
% operated on *reversed* lists of goal_path_steps in order to avoid the O(N^2)
% complexity of repeatedly appending new steps to the end of a list.
% Replacing the list(goal_path_step) type with separate forward_goal_path and
% reverse_goal_path types made understanding the code doing that significantly
% easier.
%
% Later on, other compiler passes also needed ways to uniquely identify
% each subgoal in a procedure body. Using goal paths for this works,
% but it is overkill, and therefore adds unnecessary overhead. This is
% why we added goal_ids. A map whose keys are goal_ids can be expected to be
% *much* faster than a map whose keys are (forward or reverse) goal paths,
% because comparing two integers is much less work than comparing two nested
% (possibly deeply nested) structured terms.
%
% Having two separate slots in a hlds_goal_info for a goal_id and a goal path
% would also be wasteful. This is why hlds_goal_infos have only one slot,
% containing a goal_id. Nevertheless, code that wants to do so can convert
% this goal_id into a goal path (of either kind), because the code in the
% compiler that allocates goal ids also returns a map, the containing goal
% map, to make this possible. This data structure maps each goal id to the id
% of its innermost containing goal (if there is one), and the step from
% that containing goal to this one.
%
% THE FUTURE
%
% There are some compiler modules that use goal_paths to identify subgoals,
% because goal_ids did not yet exist when they were written. Later modules
% use goal_ids for that purpose, and any new code should follow their example.
% Ideally, the use of goal paths in the future should be restricted to
% use cases where we want to communicate the identity of a goal to a human,
% who may be using e.g. mdb or mdprof.
%
%---------------------------------------------------------------------------%
:- module mdbcomp.goal_path.
:- interface.
:- import_module array.
:- import_module bimap.
:- import_module char.
:- import_module map.
:- import_module maybe.
%---------------------------------------------------------------------------%
:- type forward_goal_path
---> fgp_nil
; fgp_cons(goal_path_step, forward_goal_path).
:- type reverse_goal_path
---> rgp_nil
; rgp_cons(reverse_goal_path, goal_path_step).
:- type goal_path_string == string.
% The integers in these steps start counting at one.
:- type goal_path_step
---> step_conj(int) % which conjunct of a conjunction
; step_disj(int) % which disjunct of a disjunction
; step_switch(int, maybe_switch_num_functors)
; step_ite_cond
; step_ite_then
; step_ite_else
; step_neg
; step_scope(maybe_cut)
; step_lambda
; step_try
; step_atomic_main
; step_atomic_orelse(int).
% For switches on values of discriminated union types, this will be
% known_num_functors_in_type(N), where N is the number of function symbols
% in the type of the variable being switched.
%
% For switches on builtin types such as integer and string, for which
% the number of function symbols in the type is effectively infinite,
% this will be unknown_num_functors_in_type.
%
:- type maybe_switch_num_functors
---> unknown_num_functors_in_type
; known_num_functors_in_type(int).
% Does the scope goal have a different determinism inside than outside?
:- type maybe_cut
---> scope_is_cut
; scope_is_no_cut.
%---------------------%
% Append a goal path step onto the end of a goal path.
%
:- func goal_path_add_at_end(forward_goal_path, goal_path_step) =
forward_goal_path.
% Append a goal path step onto the end of a reverse goal path.
%
:- func rev_goal_path_add_at_end(reverse_goal_path, goal_path_step) =
reverse_goal_path.
% Remove the last item from the goal path, returning it and the new
% goal path.
%
:- pred goal_path_remove_last(forward_goal_path::in, forward_goal_path::out,
goal_path_step::out) is semidet.
% Get the last item from the goal path. This fails if the goal path is
% empty.
%
:- pred goal_path_get_last(forward_goal_path::in, goal_path_step::out)
is semidet.
% Remove the first item from the goal path, returning it and the new
% goal path.
%
:- pred goal_path_remove_first(forward_goal_path::in, forward_goal_path::out,
goal_path_step::out) is semidet.
% Get the first item from the goal path. This fails if the goal path is
% empty.
%
:- pred goal_path_get_first(forward_goal_path::in, goal_path_step::out)
is semidet.
% Remove the last item from the goal path, returning it and the new
% goal path.
%
:- pred rev_goal_path_remove_last(reverse_goal_path::in,
reverse_goal_path::out, goal_path_step::out) is semidet.
% Get the last item from the goal path. This fails if the goal path is
% empty.
%
:- pred rev_goal_path_get_last(reverse_goal_path::in, goal_path_step::out)
is semidet.
%---------------------%
% goal_path_inside(PathA, PathB):
%
% Succeed if PathB denotes a goal *inside* the goal denoted by PathA.
% (It considers a goal to be inside itself.)
%
:- pred goal_path_inside(forward_goal_path::in, forward_goal_path::in)
is semidet.
:- pred rev_goal_path_inside(reverse_goal_path::in, reverse_goal_path::in)
is semidet.
% goal_path_inside_relative(PathA, PathB, RelativePath):
%
% As goal_path_inside, except that it also returns RelativePath, which
% denotes the same goal that PathB denotes, only from GoalA's perspective.
%
:- pred goal_path_inside_relative(forward_goal_path::in,
forward_goal_path::in, forward_goal_path::out) is semidet.
:- pred rev_goal_path_inside_relative(reverse_goal_path::in,
reverse_goal_path::in, reverse_goal_path::out) is semidet.
%---------------------%
% Convert one kind of goal path into the other.
%
:- pred rgp_to_fgp(reverse_goal_path::in, forward_goal_path::out) is det.
:- pred fgp_to_rgp(forward_goal_path::in, reverse_goal_path::out) is det.
%---------------------%
% Remove information from the goal path that depends on type information.
%
% This is necessary when using goal paths to lookup a map within the deep
% profiler. The goal paths used to perform the query cannot construct the
% parts of the goal paths that depend on type information.
%
:- pred rev_goal_path_remove_type_info(reverse_goal_path::in,
reverse_goal_path::out) is det.
%---------------------%
% Converts a string to a forward goal path, failing if the string
% is not a valid goal path.
%
:- pred goal_path_from_string(string::in, forward_goal_path::out) is semidet.
% Converts a string to a forward goal path, aborting if the string
% is not a valid goal path.
%
:- pred goal_path_from_string_det(string::in, forward_goal_path::out) is det.
% Converts a string to a reverse goal path, failing if the string
% is not a valid goal path.
%
:- pred rev_goal_path_from_string(string::in, reverse_goal_path::out)
is semidet.
% Converts a string to a reverse goal path, aborting if the string
% is not a valid goal path.
%
:- pred rev_goal_path_from_string_det(string::in, reverse_goal_path::out)
is det.
% Converts a string to a goal path step, failing if the string is not
% a valid goal path step.
%
:- pred goal_path_step_from_string(string::in, goal_path_step::out) is semidet.
% Is this character the one that ends each goal path step?
%
:- pred is_goal_path_separator(char::in) is semidet.
%---------------------%
% Convert the goal path to its string representation. The resulting string
% is guaranteed to be acceptable to path_from_string_det.
%
:- func goal_path_to_string(forward_goal_path) = string.
% Convert the goal path to its string representation. The resulting string
% is guaranteed to be acceptable to rev_path_from_string_det.
%
:- func rev_goal_path_to_string(reverse_goal_path) = string.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
% We identify individual (sub)goals in clause bodies and procedure bodies
% with a goal id. The whole goal is goal_id(1u), and its subgoals are
% goal_id(N) for N > 1u. The goal id goal_id(0u) is reserved for use by
% goal_id_for_head_constraints.
%
:- type goal_id
---> goal_id(uint).
% Return the goal_id that identifies the whole of a procedure's body.
%
:- func whole_body_goal_id = goal_id.
% Return a recognizably invalid goal id.
%
:- func invalid_goal_id = goal_id.
% Succeed iff the given goal id is valid.
%
:- pred is_valid_goal_id(goal_id::in) is semidet.
% Return a goal id on which the typeclass checking code can hang
% the constraints that need to be proven for clause heads.
%
% This goal id will be *distinct* from all goal_ids that can be hung
% on goals in the bodies of clauses, i.e. it will NOT be a valid goal id.
%
:- func goal_id_for_head_constraints = goal_id.
%---------------------------------------------------------------------------%
:- type containing_goal
---> whole_body_goal
% This goal is the entire body of its procedure;
% there is no larger goal containing it.
; containing_goal(goal_id, goal_path_step).
% This goal is contained immediately inside the larger goal
% identified by the goal_id, from which you need to take the
% given goal_path_step to get to this goal.
%
% The goal_id of the containing goal is guaranteed to be
% less than the goal_id of this goal.
:- type containing_goal_map == map(goal_id, containing_goal).
:- type goal_forward_path_map == map(goal_id, forward_goal_path).
:- type goal_reverse_path_map == map(goal_id, reverse_goal_path).
:- type goal_reverse_path_bimap == bimap(goal_id, reverse_goal_path).
% Convert a goal_id to a forward goal path.
%
:- func goal_id_to_forward_path(containing_goal_map, goal_id) =
forward_goal_path.
% Convert a goal_id to a reverse goal path.
%
:- func goal_id_to_reverse_path(containing_goal_map, goal_id) =
reverse_goal_path.
% Given a containing_goal_map, create a map that maps each goal_id in it
% to a forward goal path.
%
:- func create_forward_goal_path_map(containing_goal_map) =
goal_forward_path_map.
% Given a containing_goal_map, create a map that maps each goal_id in it
% to a reverse goal path.
%
:- func create_reverse_goal_path_map(containing_goal_map) =
goal_reverse_path_map.
% Given a containing_goal_map, create a map that maps each goal_id in it
% to a reverse goal path, and back.
%
:- func create_reverse_goal_path_bimap(containing_goal_map) =
goal_reverse_path_bimap.
% goal_id_inside(ContainingGoalMap, GoalIdA, GoalIdB):
%
% Succeeds if GoalIdB denotes a goal *inside* the goal denoted by GoalIdA.
% (It considers a goal to be inside itself.)
%
:- pred goal_id_inside(containing_goal_map::in,
goal_id::in, goal_id::in) is semidet.
%---------------------------------------------------------------------------%
:- type goal_attr_array(T)
---> goal_attr_array(array(maybe(T))).
% This isn't really unique. See the comments on the `uniq_array' type
% in library/array.m.
%
:- inst uniq_goal_attr_array for goal_attr_array/1
---> goal_attr_array(uniq_array).
:- mode gaa_di == di(uniq_goal_attr_array).
:- mode gaa_uo == out(uniq_goal_attr_array).
% create_goal_id_array(LastGoalId) = Array.
%
% Create an array of the correct size to label all the goals
% up to and including LastGoalId.
%
:- func create_goal_id_array(goal_id) = goal_attr_array(T).
:- mode create_goal_id_array(in) = gaa_uo is det.
% create_goal_id_array(LastGoalId, Default) = Array.
%
% As above, except a default value is provided for array elements.
%
:- func create_goal_id_array(goal_id, T) = goal_attr_array(T).
:- mode create_goal_id_array(in, in) = gaa_uo is det.
% update_goal_attribute(GoalId, Attribute, !Array),
%
% Make Attribute the new attribute for GoalId in !:Array.
%
:- pred update_goal_attribute(goal_id::in, T::in,
goal_attr_array(T)::gaa_di, goal_attr_array(T)::gaa_uo) is det.
% get_goal_attribute(Array, GoalId) = Attribute.
%
% Get a goal attribute.
%
:- func get_goal_attribute_det(goal_attr_array(T), goal_id) = T.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module assoc_list.
:- import_module list.
:- import_module pair.
:- import_module require.
:- import_module string.
:- import_module uint.
%---------------------------------------------------------------------------%
goal_path_add_at_end(fgp_nil, NewStep) = fgp_cons(NewStep, fgp_nil).
goal_path_add_at_end(fgp_cons(OldStep, GoalPath0), NewStep) =
fgp_cons(OldStep, GoalPath) :-
GoalPath = goal_path_add_at_end(GoalPath0, NewStep).
rev_goal_path_add_at_end(GoalPath0, NewStep) = GoalPath :-
GoalPath = rgp_cons(GoalPath0, NewStep).
goal_path_remove_last(fgp_cons(HeadStep, TailSteps),
AllButLastGoalPath, LastStep) :-
goal_path_remove_last_loop(HeadStep, TailSteps,
AllButLastGoalPath, LastStep).
:- pred goal_path_remove_last_loop(goal_path_step::in, forward_goal_path::in,
forward_goal_path::out, goal_path_step::out) is det.
goal_path_remove_last_loop(Head, fgp_nil, fgp_nil, Head).
goal_path_remove_last_loop(Head, fgp_cons(TailHead, TailTail),
AllButLastGoalPath, LastStep) :-
goal_path_remove_last_loop(TailHead, TailTail,
AllButLastGoalPath0, LastStep),
AllButLastGoalPath = fgp_cons(Head, AllButLastGoalPath0).
goal_path_get_last(fgp_cons(HeadStep, TailSteps), LastStep) :-
goal_path_last_loop(HeadStep, TailSteps, LastStep).
:- pred goal_path_last_loop(goal_path_step::in, forward_goal_path::in,
goal_path_step::out) is det.
goal_path_last_loop(Head, fgp_nil, Head).
goal_path_last_loop(_Head, fgp_cons(TailHead, TailTail), LastStep) :-
goal_path_last_loop(TailHead, TailTail, LastStep).
goal_path_remove_first(fgp_cons(FirstStep, OtherSteps), OtherSteps,
FirstStep).
goal_path_get_first(GoalPath, FirstStep) :-
goal_path_remove_first(GoalPath, _, FirstStep).
rev_goal_path_remove_last(rgp_cons(GoalPath, LastStep), GoalPath, LastStep).
rev_goal_path_get_last(rgp_cons(_, LastStep), LastStep).
%---------------------------------------------------------------------------%
goal_path_inside(PathA, PathB) :-
goal_path_inside_relative(PathA, PathB, _).
rev_goal_path_inside(RevPathA, RevPathB) :-
rev_goal_path_inside_relative(RevPathA, RevPathB, _).
goal_path_inside_relative(fgp_nil, PathB, PathB).
goal_path_inside_relative(fgp_cons(StepA, PathA), fgp_cons(StepB, PathB),
RelativePath) :-
StepA = StepB,
goal_path_inside_relative(PathA, PathB, RelativePath).
rev_goal_path_inside_relative(RevPathA, RevPathB, RevRelativePath) :-
% XXX It would be more efficient if we could do this test
% without having to translate twice the part of RevPathB that
% goal_path_inside_relative returns but does not test.
rgp_to_fgp(RevPathA, PathA),
rgp_to_fgp(RevPathB, PathB),
goal_path_inside_relative(PathA, PathB, RelativePath),
fgp_to_rgp(RelativePath, RevRelativePath).
%---------------------------------------------------------------------------%
rgp_to_fgp(ReverseGoalPath, ForwardGoalPath) :-
rgp_to_fgp_acc(ReverseGoalPath, fgp_nil, ForwardGoalPath).
:- pred rgp_to_fgp_acc(reverse_goal_path::in,
forward_goal_path::in, forward_goal_path::out) is det.
rgp_to_fgp_acc(rgp_nil, !ForwardGoalPath).
rgp_to_fgp_acc(rgp_cons(EarlierSteps, LastStep), !ForwardGoalPath) :-
!:ForwardGoalPath = fgp_cons(LastStep, !.ForwardGoalPath),
rgp_to_fgp_acc(EarlierSteps, !ForwardGoalPath).
fgp_to_rgp(ForwardGoalPath, ReverseGoalPath) :-
fgp_to_rgp_acc(ForwardGoalPath, rgp_nil, ReverseGoalPath).
:- pred fgp_to_rgp_acc(forward_goal_path::in,
reverse_goal_path::in, reverse_goal_path::out) is det.
fgp_to_rgp_acc(fgp_nil, !ReverseGoalPath).
fgp_to_rgp_acc(fgp_cons(FirstStep, LaterSteps), !ReverseGoalPath) :-
!:ReverseGoalPath = rgp_cons(!.ReverseGoalPath, FirstStep),
fgp_to_rgp_acc(LaterSteps, !ReverseGoalPath).
%---------------------------------------------------------------------------%
rev_goal_path_remove_type_info(rgp_nil, rgp_nil).
rev_goal_path_remove_type_info(rgp_cons(Steps0, Step0),
rgp_cons(Steps, Step)) :-
goal_path_step_remove_type_info(Step0, Step),
rev_goal_path_remove_type_info(Steps0, Steps).
:- pred goal_path_step_remove_type_info(goal_path_step::in,
goal_path_step::out) is det.
goal_path_step_remove_type_info(!Step) :-
(
( !.Step = step_conj(_)
; !.Step = step_disj(_)
; !.Step = step_ite_cond
; !.Step = step_ite_then
; !.Step = step_ite_else
; !.Step = step_neg
; !.Step = step_scope(_)
; !.Step = step_lambda
; !.Step = step_try
; !.Step = step_atomic_main
; !.Step = step_atomic_orelse(_)
)
;
!.Step = step_switch(N, _),
!:Step = step_switch(N, unknown_num_functors_in_type)
).
%---------------------------------------------------------------------------%
goal_path_from_string(GoalPathStr, GoalPath) :-
StepStrs = string.words_separator(is_goal_path_separator, GoalPathStr),
goal_path_from_strings(StepStrs, GoalPath).
:- pred goal_path_from_strings(list(string)::in, forward_goal_path::out)
is semidet.
goal_path_from_strings([], fgp_nil).
goal_path_from_strings([Str | Strs], fgp_cons(HeadStep, LaterSteps)) :-
goal_path_step_from_string(Str, HeadStep),
goal_path_from_strings(Strs, LaterSteps).
goal_path_from_string_det(GoalPathStr, GoalPath) :-
( if goal_path_from_string(GoalPathStr, GoalPathPrime) then
GoalPath = GoalPathPrime
else
unexpected($pred, "goal_path_from_string failed")
).
rev_goal_path_from_string(GoalPathStr, GoalPath) :-
StepStrs = string.words_separator(is_goal_path_separator, GoalPathStr),
list.reverse(StepStrs, RevStepStrs),
rev_goal_path_from_rev_strings(RevStepStrs, GoalPath).
:- pred rev_goal_path_from_rev_strings(list(string)::in,
reverse_goal_path::out) is semidet.
rev_goal_path_from_rev_strings([], rgp_nil).
rev_goal_path_from_rev_strings([Str | Strs], rgp_cons(HeadSteps, TailStep)) :-
rev_goal_path_from_rev_strings(Strs, HeadSteps),
goal_path_step_from_string(Str, TailStep).
rev_goal_path_from_string_det(GoalPathStr, GoalPath) :-
( if rev_goal_path_from_string(GoalPathStr, GoalPathPrime) then
GoalPath = GoalPathPrime
else
unexpected($pred, "rev_goal_path_from_string failed")
).
%---------------------%
goal_path_step_from_string(String, Step) :-
string.first_char(String, FirstChar, RestStr),
(
( FirstChar = '?', Step = step_ite_cond
; FirstChar = 't', Step = step_ite_then
; FirstChar = 'e', Step = step_ite_else
; FirstChar = ('~'), Step = step_neg
; FirstChar = 'r', Step = step_try
; FirstChar = ('='), Step = step_lambda
; FirstChar = 'a', Step = step_atomic_main
),
RestStr = ""
;
FirstChar = 'c',
string.to_int(RestStr, N),
Step = step_conj(N)
;
FirstChar = 'd',
string.to_int(RestStr, N),
Step = step_disj(N)
;
FirstChar = 'o',
string.to_int(RestStr, N),
Step = step_atomic_orelse(N)
;
FirstChar = 's',
string.words_separator(unify('-'), RestStr) = [NStr, MStr],
string.to_int(NStr, N),
( if MStr = "na" then % "na" is short for "not applicable"
MaybeM = unknown_num_functors_in_type
else
string.to_int(MStr, M),
MaybeM = known_num_functors_in_type(M)
),
Step = step_switch(N, MaybeM)
;
FirstChar = 'q',
(
RestStr = "",
Step = step_scope(scope_is_no_cut)
;
RestStr = "!",
Step = step_scope(scope_is_cut)
)
).
is_goal_path_separator(';').
%---------------------------------------------------------------------------%
goal_path_to_string(GoalPath) = GoalPathStr :-
StepStrs = goal_path_to_strings(GoalPath),
string.append_list(StepStrs, GoalPathStr).
:- func goal_path_to_strings(forward_goal_path) = list(string).
goal_path_to_strings(fgp_nil) = [].
goal_path_to_strings(fgp_cons(Step, Steps)) = [Str | Strs] :-
Str = goal_path_step_to_string(Step),
Strs = goal_path_to_strings(Steps).
rev_goal_path_to_string(GoalPath) = GoalPathStr :-
RevStepStrs = rev_goal_path_to_strings(GoalPath),
list.reverse(RevStepStrs, StepStrs),
string.append_list(StepStrs, GoalPathStr).
:- func rev_goal_path_to_strings(reverse_goal_path) = list(string).
rev_goal_path_to_strings(rgp_nil) = [].
rev_goal_path_to_strings(rgp_cons(Steps, Step)) = [Str | Strs] :-
Str = goal_path_step_to_string(Step),
Strs = rev_goal_path_to_strings(Steps).
:- func goal_path_step_to_string(goal_path_step) = string.
goal_path_step_to_string(step_conj(N)) = "c" ++ int_to_string(N) ++ ";".
goal_path_step_to_string(step_disj(N)) = "d" ++ int_to_string(N) ++ ";".
goal_path_step_to_string(step_switch(N, known_num_functors_in_type(M))) =
"s" ++ int_to_string(N) ++ "-" ++ int_to_string(M) ++ ";".
goal_path_step_to_string(step_switch(N, unknown_num_functors_in_type)) =
"s" ++ int_to_string(N) ++ "-na;". % short for "not applicable"
goal_path_step_to_string(step_ite_cond) = "?;".
goal_path_step_to_string(step_ite_then) = "t;".
goal_path_step_to_string(step_ite_else) = "e;".
goal_path_step_to_string(step_neg) = "~;".
goal_path_step_to_string(step_scope(scope_is_cut)) = "q!;".
goal_path_step_to_string(step_scope(scope_is_no_cut)) = "q;".
goal_path_step_to_string(step_try) = "r;".
goal_path_step_to_string(step_lambda) = "=;".
goal_path_step_to_string(step_atomic_main) = "a;".
goal_path_step_to_string(step_atomic_orelse(N)) =
"o" ++ int_to_string(N) ++ ";".
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
whole_body_goal_id = goal_id(1u).
invalid_goal_id = goal_id(0u).
is_valid_goal_id(goal_id(GoalIdNum)) :-
GoalIdNum > 0u.
goal_id_for_head_constraints = goal_id(0u).
% Note that this is NOT a valid goal_id for a goal. Not being able
% to confuse the goal_id on which head constraints are hung with the
% goal_id of an actual goal is the POINT of this function.
%---------------------------------------------------------------------------%
goal_id_to_forward_path(ContainingGoalMap, GoalId) = GoalPath :-
RevGoalPath = goal_id_to_reverse_path(ContainingGoalMap, GoalId),
rgp_to_fgp(RevGoalPath, GoalPath).
goal_id_to_reverse_path(ContainingGoalMap, GoalId) = GoalPath :-
map.lookup(ContainingGoalMap, GoalId, ContainingGoal),
(
ContainingGoal = whole_body_goal,
GoalPath = rgp_nil
;
ContainingGoal = containing_goal(ParentGoalId, LastStep),
EarlierPath = goal_id_to_reverse_path(ContainingGoalMap, ParentGoalId),
GoalPath = rgp_cons(EarlierPath, LastStep)
).
create_forward_goal_path_map(ContainingGoalMap) = ForwardGoalPathMap :-
ReverseGoalPathMap = create_reverse_goal_path_map(ContainingGoalMap),
map.map_values_only(rgp_to_fgp, ReverseGoalPathMap, ForwardGoalPathMap).
create_reverse_goal_path_map(ContainingGoalMap) = ReverseGoalPathMap :-
map.to_assoc_list(ContainingGoalMap, ContainingGoalList),
create_reverse_goal_path_map_acc(ContainingGoalList,
map.init, ReverseGoalPathMap).
:- pred create_reverse_goal_path_map_acc(
assoc_list(goal_id, containing_goal)::in,
map(goal_id, reverse_goal_path)::in, map(goal_id, reverse_goal_path)::out)
is det.
create_reverse_goal_path_map_acc([], !ReverseGoalPathMap).
create_reverse_goal_path_map_acc([Head | Tail], !ReverseGoalPathMap) :-
Head = GoalId - ContainingGoal,
(
ContainingGoal = whole_body_goal,
GoalReversePath = rgp_nil
;
ContainingGoal = containing_goal(ContainingGoalId, Step),
map.lookup(!.ReverseGoalPathMap, ContainingGoalId,
ContainingGoalReversePath),
GoalReversePath = rgp_cons(ContainingGoalReversePath, Step)
),
map.det_insert(GoalId, GoalReversePath, !ReverseGoalPathMap),
create_reverse_goal_path_map_acc(Tail, !ReverseGoalPathMap).
create_reverse_goal_path_bimap(ContainingGoalMap) = ReverseGoalPathBiMap :-
map.to_assoc_list(ContainingGoalMap, ContainingGoalList),
create_reverse_goal_path_bimap_acc(ContainingGoalList,
bimap.init, ReverseGoalPathBiMap).
:- pred create_reverse_goal_path_bimap_acc(
assoc_list(goal_id, containing_goal)::in,
bimap(goal_id, reverse_goal_path)::in,
bimap(goal_id, reverse_goal_path)::out) is det.
create_reverse_goal_path_bimap_acc([], !ReverseGoalPathBiMap).
create_reverse_goal_path_bimap_acc([Head | Tail], !ReverseGoalPathBiMap) :-
Head = GoalId - ContainingGoal,
(
ContainingGoal = whole_body_goal,
GoalReversePath = rgp_nil
;
ContainingGoal = containing_goal(ContainingGoalId, Step),
bimap.lookup(!.ReverseGoalPathBiMap, ContainingGoalId,
ContainingGoalReversePath),
GoalReversePath = rgp_cons(ContainingGoalReversePath, Step)
),
bimap.det_insert(GoalId, GoalReversePath, !ReverseGoalPathBiMap),
create_reverse_goal_path_bimap_acc(Tail, !ReverseGoalPathBiMap).
goal_id_inside(ContainingGoalId, GoalIdA, GoalIdB) :-
(
GoalIdB = GoalIdA
;
map.lookup(ContainingGoalId, GoalIdB, GoalContainingB),
GoalContainingB = containing_goal(ParentGoalIdB, _),
goal_id_inside(ContainingGoalId, GoalIdA, ParentGoalIdB)
).
%---------------------------------------------------------------------------%
create_goal_id_array(goal_id(LastGoalIdNum)) =
goal_attr_array(array.init(uint.cast_to_int(LastGoalIdNum + 1u), no)).
create_goal_id_array(goal_id(LastGoalIdNum), Default) =
goal_attr_array(array.init(uint.cast_to_int(LastGoalIdNum + 1u),
yes(Default))).
update_goal_attribute(goal_id(Index), Value,
goal_attr_array(!.Array), goal_attr_array(!:Array)) :-
array.set(uint.cast_to_int(Index), yes(Value), !Array).
get_goal_attribute_det(goal_attr_array(Array), goal_id(Index)) = Attr :-
MaybeAttr = array.lookup(Array, uint.cast_to_int(Index)),
(
MaybeAttr = yes(Attr)
;
MaybeAttr = no,
unexpected($pred, "Goal attribute array slot empty")
).
%---------------------------------------------------------------------------%
:- end_module mdbcomp.goal_path.
%---------------------------------------------------------------------------%