%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1993-2000,2002-2007, 2009-2011 The University of Melbourne.
% Copyright (C) 2014-2024 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%---------------------------------------------------------------------------%
%
% File: varset.m.
% Main author: fjh.
% Stability: low.
%
% This file provides facilities for manipulating collections of variables,
% through the 'varset' abstract data type. These variables are object-level
% variables, and are represented as ground terms, so the right way to think
% of them is as "variable ids" rather than Prolog-style variables.
%
% A varset may record a name and/or a value (binding) with each variable.
%
% Many situations require dealing with several distinct sets of variables
% that should never be mixed together. For example, a compiler may handle
% both program variables and type variables, and it does not make sense
% to a have a single varset containing both program variables and type
% variables. The varsets provided by this module are thus parameterized;
% the compiler can use e.g. varset(prog_var_type) to hold program variables
% and varset(tvar_type) to hold type variables. Since all operations on
% two or more varsets require agreement on the argument of the varset/1
% type constructor, any accidental mixup of different instances of varset/1
% is guaranteed to be caught by the compiler.
%
% In situations in which this is not a concern, programmers may use
% the standard generic varset instance.
%
% Note that varset.m and term.m are strongly coupled together, meaning that
% they each need the other. The reason why they have not been merged into
% one larger module is that many user modules call predicates and functions
% from just one of these two modules, even though, through that one module,
% they implicitly depend on the other as well.
%
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- module varset.
:- interface.

:- import_module assoc_list.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module set.
:- import_module term.

%---------------------------------------------------------------------------%

:- type varset(T).
:- type varset == varset(generic).

%---------------------%

    % Construct an empty varset.
    %
:- func init = varset(T).
:- pred init(varset(T)::out) is det.

    % Check whether a varset is empty.
    %
:- pred is_empty(varset(T)::in) is semidet.

%---------------------%

    % Create a new variable.
    %
:- pred new_var(var(T)::out, varset(T)::in, varset(T)::out) is det.

    % Create a new named variable.
    %
:- pred new_named_var(string::in, var(T)::out,
    varset(T)::in, varset(T)::out) is det.

    % Create a new named variable with a unique (w.r.t. the varset) number
    % appended to the name.
    %
:- pred new_uniquely_named_var(string::in, var(T)::out,
    varset(T)::in, varset(T)::out) is det.

    % Create a new variable, and maybe give it a name.
    %
:- pred new_maybe_named_var(maybe(string)::in, var(T)::out,
    varset(T)::in, varset(T)::out) is det.

    % Create multiple new variables.
    % Throws an exception if a negative number of new variables
    % is requested.
    %
:- pred new_vars(int::in, list(var(T))::out,
    varset(T)::in, varset(T)::out) is det.

%---------------------%

    % Delete the name and value for a variable.
    %
:- func delete_var(varset(T), var(T)) = varset(T).
:- pred delete_var(var(T)::in, varset(T)::in, varset(T)::out) is det.

    % Delete the names and values for a list of variables.
    %
:- func delete_vars(varset(T), list(var(T))) = varset(T).
:- pred delete_vars(list(var(T))::in, varset(T)::in, varset(T)::out) is det.

    % Delete the names and values for a sorted list of variables.
    % (If the list is not sorted, the predicate or function will
    % either throw an exception or return incorrect output.)
    %
:- func delete_sorted_vars(varset(T), list(var(T))) = varset(T).
:- pred delete_sorted_vars(list(var(T))::in,
    varset(T)::in, varset(T)::out) is det.

%---------------------%

    % Return a list of all the variables in a varset.
    %
:- func vars(varset(T)) = list(var(T)).
:- pred vars(varset(T)::in, list(var(T))::out) is det.

%---------------------%

    % Set the name of a variable.
    %
:- func name_var(varset(T), var(T), string) = varset(T).
:- pred name_var(var(T)::in, string::in,
    varset(T)::in, varset(T)::out) is det.

    % Unset the name of a variable.
    %
:- func unname_var(varset(T), var(T)) = varset(T).
:- pred unname_var(var(T)::in, varset(T)::in, varset(T)::out) is det.

    % Look up the name of a variable;
    % If it doesn't have one, return a default name consisting of two parts:
    % "V_" as a prefix, followed by a unique number. This is meant to evoke
    % "variable number N".
    %
:- func lookup_name(varset(T), var(T)) = string.
:- pred lookup_name(varset(T)::in, var(T)::in, string::out) is det.

    % Look up the name of a variable;
    % if it doesn't have one, create one using the specified prefix.
    %
:- func lookup_name_default_prefix(varset(T), var(T), string) = string.
:- pred lookup_name_default_prefix(varset(T)::in, var(T)::in, string::in,
    string::out) is det.

    % The old, now obsolete name for what is now lookup_name_default_prefix.
    %
:- func lookup_name(varset(T), var(T), string) = string.
:- pred lookup_name(varset(T)::in, var(T)::in, string::in, string::out)
    is det.
:- pragma obsolete(func(lookup_name/3), [varset.lookup_name_default_prefix/3]).
:- pragma obsolete(pred(lookup_name/4), [varset.lookup_name_default_prefix/4]).

    % Look up the name of a variable;
    % fail if it doesn't have one.
    %
:- pred search_name(varset(T)::in, var(T)::in, string::out) is semidet.

%---------------------%

    % Bind a value to a variable.
    % This will overwrite any existing binding.
    %
:- func bind_var(varset(T), var(T), term(T)) = varset(T).
:- pred bind_var(var(T)::in, term(T)::in,
    varset(T)::in, varset(T)::out) is det.

    % Bind a set of terms to a set of variables.
    %
:- func bind_vars(varset(T), substitution(T)) = varset(T).
:- pred bind_vars(substitution(T)::in,
    varset(T)::in, varset(T)::out) is det.

    % Look up the value of a variable.
    %
:- pred search_var(varset(T)::in, var(T)::in, term(T)::out) is semidet.

%---------------------%

    % Get the bindings for all the bound variables.
    %
:- func lookup_vars(varset(T)) = substitution(T).
:- pred lookup_vars(varset(T)::in, substitution(T)::out) is det.
:- pragma obsolete(func(lookup_vars/1), [varset.get_bindings/1]).
:- pragma obsolete(pred(lookup_vars/2), [varset.get_bindings/2]).

    % Get the bindings for all the bound variables.
    %
:- func get_bindings(varset(T)) = substitution(T).
:- pred get_bindings(varset(T)::in, substitution(T)::out) is det.

    % Set the bindings for all the bound variables.
    %
:- func set_bindings(varset(T), substitution(T)) = varset(T).
:- pred set_bindings(substitution(T)::in,
    varset(T)::in, varset(T)::out) is det.

%---------------------%

    % Combine two different varsets, renaming apart:
    % merge_renaming(VarSet0, NewVarSet, VarSet, Renaming) is true
    % iff VarSet is the varset that results from joining a suitably renamed
    % version of NewVarSet to VarSet0. (Any bindings in NewVarSet are ignored.)
    % Renaming will map each variable in NewVarSet to the corresponding
    % fresh variable in VarSet.
    %
:- pred merge_renaming(varset(T)::in, varset(T)::in,
    varset(T)::out, renaming(T)::out) is det.

    % Same as merge_renaming, except that the names of variables
    % in NewVarSet are not included in the final varset.
    % This is useful if create_name_var_map needs to be used
    % on the resulting varset.
    %
:- pred merge_renaming_without_names(varset(T)::in, varset(T)::in,
    varset(T)::out, renaming(T)::out) is det.

    % merge(VarSet0, NewVarSet, Terms0, VarSet, Terms):
    %
    % As merge_renaming, except instead of returning the renaming,
    % this predicate applies it to the given list of terms.
    %
:- pred merge(varset(T)::in, varset(T)::in, list(term(T))::in,
    varset(T)::out, list(term(T))::out) is det.

    % Same as merge, except that the names of variables
    % in NewVarSet are not included in the final varset.
    % This is useful if create_name_var_map needs to be used
    % on the resulting varset.
    %
:- pred merge_without_names(varset(T)::in, varset(T)::in, list(term(T))::in,
    varset(T)::out, list(term(T))::out) is det.

%---------------------%

    % Create a map from names to variables.
    % Each name is mapped to only one variable, even if a name is
    % shared by more than one variable. Therefore this predicate
    % is only really useful if it is already known that no two
    % variables share the same name.
    %
:- func create_name_var_map(varset(T)) = map(string, var(T)).
:- pred create_name_var_map(varset(T)::in, map(string, var(T))::out)
    is det.

    % Return an association list giving the name of each variable.
    % Every variable has an entry in the returned association list,
    % even if it shares its name with another variable.
    %
:- func var_name_list(varset(T)) = assoc_list(var(T), string).
:- pred var_name_list(varset(T)::in, assoc_list(var(T), string)::out)
    is det.

    % Given a list of variable and varset in which some variables have
    % no name but some other variables may have the same name,
    % return another varset in which every variable has a unique name.
    % If necessary, names will have suffixes added on the end;
    % the second argument gives the suffix to use.
    %
:- func ensure_unique_names(list(var(T)), string, varset(T)) = varset(T).
:- pred ensure_unique_names(list(var(T))::in, string::in,
    varset(T)::in, varset(T)::out) is det.

    % Unname all variables whose explicitly given names have the form
    % of the default names used by lookup_name, i.e. "V_" followed by
    % an integer.
    %
    % This predicate is intended to be used in situations where
    % a term has been read in after being written out. The process of
    % writing out the term requires giving every variable a name
    % that can be written out, even variables that until then did not
    % have names. If these variables are given names of the default form, then,
    % after the written-out term is read back in, this predicate will recreate
    % the original varset, including the variables without names.
    %
:- pred undo_default_names(varset(T)::in, varset(T)::out) is det.

%---------------------%

    % Given a varset and a set of variables, remove the names
    % and values of any other variables stored in the varset.
    %
:- func select(varset(T), set(var(T))) = varset(T).
:- pred select(set(var(T))::in, varset(T)::in, varset(T)::out) is det.

    % Given a varset and a list of variables, construct a new varset
    % containing one variable for each one in the list, and no others.
    % Also return a substitution mapping the selected variables in the
    % original varset into variables in the new varset. The relative
    % ordering of variables in the original varset is maintained.
    %
:- pred squash(varset(T)::in, list(var(T))::in,
    varset(T)::out, renaming(T)::out) is det.

    % Coerce the types of the variables in a varset.
    %
:- func coerce(varset(T)) = varset(U).
:- pred coerce(varset(T)::in, varset(U)::out) is det.

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- implementation.

% Everything below here is not intended to be part of the public interface,
% and will not be included in the Mercury library reference manual.

:- interface.

    % Returns the highest numbered variable returned from this varset's
    % var_supply.
    %
:- func max_var(varset(T)) = var(T).

:- func num_allocated(varset(T)) = int.

:- pred get_var_names(varset(T)::in, map(var(T), string)::out) is det.

:- pred construct_varset(int::in, map(var(T), string)::in,
    varset(T)::out) is det.

%---------------------------------------------------------------------------%

:- implementation.

:- import_module int.
:- import_module pair.
:- import_module require.
:- import_module string.
:- import_module term_subst.

:- type varset(T)
    --->    varset(
                var_supply  :: var_supply(T),
                var_names   :: map(var(T), string),
                var_values  :: map(var(T), term(T))
            ).

%---------------------------------------------------------------------------%

init = VarSet :-
    varset.init(VarSet).

init(VarSet) :-
    term.init_var_supply(VarSupply),
    map.init(Names),
    map.init(Values),
    VarSet = varset(VarSupply, Names, Values).

%---------------------------------------------------------------------------%

is_empty(varset(VarSupply, _, _)) :-
    term.init_var_supply(VarSupply).

%---------------------------------------------------------------------------%

new_var(Var, !VarSet) :-
    MaxId0 = !.VarSet ^ var_supply,
    term.create_var(Var, MaxId0, MaxId),
    !VarSet ^ var_supply := MaxId.

new_named_var(Name, Var, !VarSet) :-
    !.VarSet = varset(MaxId0, Names0, Values),
    term.create_var(Var, MaxId0, MaxId),
    map.set(Var, Name, Names0, Names),
    !:VarSet = varset(MaxId, Names, Values).

new_uniquely_named_var(Name, Var, !VarSet) :-
    !.VarSet = varset(MaxId0, Names0, Values),
    term.create_var(Var, MaxId0, MaxId),
    N = term.var_to_int(Var),
    map.set(Var, string.format("%s_%d", [s(Name), i(N)]), Names0, Names),
    !:VarSet = varset(MaxId, Names, Values).

new_maybe_named_var(MaybeName, Var, !VarSet) :-
    !.VarSet = varset(MaxId0, Names0, Values),
    term.create_var(Var, MaxId0, MaxId),
    (
        MaybeName = no,
        Names = Names0
    ;
        MaybeName = yes(Name),
        map.set(Var, Name, Names0, Names)
    ),
    !:VarSet = varset(MaxId, Names, Values).

new_vars(NumVars, NewVars, !VarSet) :-
    new_vars_loop(NumVars, [], RevNewVars, !VarSet),
    % Return the new variables in order.
    list.reverse(RevNewVars, NewVars).

:- pred new_vars_loop(int::in, list(var(T))::in, list(var(T))::out,
    varset(T)::in, varset(T)::out) is det.

new_vars_loop(NumVars, !RevNewVars, !VarSet) :-
    ( if NumVars > 0 then
        varset.new_var(Var, !VarSet),
        !:RevNewVars = [Var | !.RevNewVars],
        varset.new_vars_loop(NumVars - 1, !RevNewVars, !VarSet)
    else if NumVars = 0 then
        true
    else
        unexpected($pred, "invalid call")
    ).

%---------------------------------------------------------------------------%

delete_var(!.VarSet, DeleteVar) = !:VarSet :-
    varset.delete_var(DeleteVar, !VarSet).

delete_var(DeleteVar, !VarSet) :-
    !.VarSet = varset(MaxId, Names0, Values0),
    map.delete(DeleteVar, Names0, Names),
    map.delete(DeleteVar, Values0, Values),
    !:VarSet = varset(MaxId, Names, Values).

%---------------------------------------------------------------------------%

delete_vars(!.VarSet, DeleteVars) = !:VarSet :-
    varset.delete_vars(DeleteVars, !VarSet).

delete_vars(DeleteVars, !VarSet) :-
    !.VarSet = varset(MaxId, Names0, Values0),
    map.delete_list(DeleteVars, Names0, Names),
    map.delete_list(DeleteVars, Values0, Values),
    !:VarSet = varset(MaxId, Names, Values).

delete_sorted_vars(!.VarSet, DeleteVars) = !:VarSet :-
    varset.delete_sorted_vars(DeleteVars, !VarSet).

delete_sorted_vars(DeleteVars, !VarSet) :-
    !.VarSet = varset(MaxId, Names0, Values0),
    map.delete_sorted_list(DeleteVars, Names0, Names),
    map.delete_sorted_list(DeleteVars, Values0, Values),
    !:VarSet = varset(MaxId, Names, Values).

%---------------------------------------------------------------------------%

vars(VarSet) = Vars :-
    varset.vars(VarSet, Vars).

vars(VarSet, Vars) :-
    MaxId = VarSet ^ var_supply,
    term.init_var_supply(N0),
    vars_loop(N0, MaxId, [], RevVars),
    list.reverse(RevVars, Vars).

:- pred vars_loop(var_supply(T)::in, var_supply(T)::in,
    list(var(T))::in, list(var(T))::out) is det.

vars_loop(Cur, Max, !RevVars) :-
    ( if Cur = Max then
        true
    else
        term.create_var(Var, Cur, Next),
        !:RevVars = [Var | !.RevVars],
        vars_loop(Next, Max, !RevVars)
    ).

%---------------------------------------------------------------------------%

name_var(!.VarSet, Var, Name) = !:VarSet :-
    varset.name_var(Var, Name, !VarSet).

name_var(Var, Name, !VarSet) :-
    Names0 = !.VarSet ^ var_names,
    map.set(Var, Name, Names0, Names),
    !VarSet ^ var_names := Names.

unname_var(!.VarSet, Var) = !:VarSet :-
    varset.unname_var(Var, !VarSet).

unname_var(Var, !VarSet) :-
    Names0 = !.VarSet ^ var_names,
    map.delete(Var, Names0, Names),
    !VarSet ^ var_names := Names.

%---------------------------------------------------------------------------%

lookup_name(VarSet, Var) = Name :-
    varset.lookup_name(VarSet, Var, Name).

lookup_name(VarSet, Var, Name) :-
    ( if varset.search_name(VarSet, Var, NamePrime) then
        Name = NamePrime
    else
        term.var_to_int(Var, VarNum),
        Name = "V_" ++ string.int_to_string(VarNum)
    ).

lookup_name_default_prefix(VarSet, Id, Prefix) = Name :-
    varset.lookup_name_default_prefix(VarSet, Id, Prefix, Name).

lookup_name_default_prefix(VarSet, Id, Prefix, Name) :-
    ( if varset.search_name(VarSet, Id, NamePrime) then
        Name = NamePrime
    else
        term.var_to_int(Id, VarNum),
        Name = Prefix ++ string.int_to_string(VarNum)
    ).

lookup_name(VarSet, Id, Prefix) = Name :-
    lookup_name_default_prefix(VarSet, Id, Prefix, Name).

lookup_name(VarSet, Id, Prefix, Name) :-
    lookup_name_default_prefix(VarSet, Id, Prefix, Name).

search_name(VarSet, Var, Name) :-
    VarSet = varset(_, Names, _),
    map.search(Names, Var, Name).

%---------------------------------------------------------------------------%

bind_var(!.VarSet, Var, Value) = !:VarSet :-
    varset.bind_var(Var, Value, !VarSet).

bind_var(Var, Value, !VarSet) :-
    Values0 = !.VarSet ^ var_values,
    map.set(Var, Value, Values0, Values),
    !VarSet ^ var_values := Values.

%---------------------------------------------------------------------------%

bind_vars(!.VarSet, Subst) = !:VarSet :-
    varset.bind_vars(Subst, !VarSet).

bind_vars(Subst, !VarSet) :-
    map.to_assoc_list(Subst, VarsValues),
    bind_vars_loop(VarsValues, !VarSet).

:- pred bind_vars_loop(assoc_list(var(T), term(T))::in, varset(T)::in,
    varset(T)::out) is det.

bind_vars_loop([], !VarSet).
bind_vars_loop([Var - Value | VarsValues], !VarSet) :-
    bind_var(Var, Value, !VarSet),
    bind_vars_loop(VarsValues, !VarSet).

%---------------------------------------------------------------------------%

search_var(VarSet, Var, Value) :-
    Values = VarSet ^ var_values,
    map.search(Values, Var, Value).

%---------------------------------------------------------------------------%

lookup_vars(VarSet) = Values :-
    varset.lookup_vars(VarSet, Values).

lookup_vars(VarSet, Values) :-
    Values = VarSet ^ var_values.

%---------------------------------------------------------------------------%

get_bindings(VarSet) = Values :-
    varset.get_bindings(VarSet, Values).

get_bindings(VarSet, Values) :-
    Values = VarSet ^ var_values.

set_bindings(!.VarSet, Values) = !:VarSet :-
    varset.set_bindings(Values, !VarSet).

set_bindings(Values, !VarSet) :-
    !VarSet ^ var_values := Values.

%---------------------------------------------------------------------------%
%
% We have two near-identical copies of code implementing two related
% but nevertheless distinct exported predicates:
%
% - merge_renaming
% - merge_renaming_without_names
%
% The code of the two must be distinct from each other if we don't want
% to pay the cost of passing around name maps unnecessarily.
%
% The approach we follow in the simple implementation of each of the above
% exported predicates is that we scan through the second varset, introducing
% a fresh variable into the first varset for each var in the second,
% and building up a renaming which maps the variables in the second varset
% into the corresponding fresh variable in the first varset.
%
% The fast implementation each of the above exported predicates does the
% same conceptual job, but with differences in detail.
%
% 1 Instead of inserting oldvar-newvar pairs into the rename map,
%   it constructs a sorted flat assoc list to be turned into a map.
%   If the two varsets have M and N vars respectively, then the simple
%   algorithm is O(N log N), while the fast one is O(N).
%
% 2 Instead of looking up the name of each variable in the second varset
%   in a map, it flattens the second varset's name map and iterates
%   over the resulting list of var-name pairs. Again, the simple algorithm
%   is O(N log N), while the fast one is O(N).
%
% 3 Instead of inserting newvar-name pairs into the updated name map,
%   it constructs a sorted flat assoc list for the new entries.
%   Constructing the updated name map requires flattening the old name map,
%   appending the new entries to it, and turning the result into a map.
%   The simple algorithm is O(N log (M+N)), since it does N insertions
%   into a map whose final size is O(M+N), while the fast algorithm is
%   O(M + N). This *may* yield a slowdown if M is much bigger than N.
%
% As its name says, merge_renaming_without_names does not update the name map,
% so the slowdown mentioned in point 3 cannot occur for them. This is why
% it has a separate heuristic.
%

:- pred use_simple_loop(int::in, int::in) is semidet.
:- pragma inline(pred(use_simple_loop/2)).

use_simple_loop(NumAllocatedA, NumAllocatedB) :-
    % XXX The numbers in this heuristic should to be determined by benchmarks.
    (
        % If NumAllocatedB (the N in the complexity analysis above)
        % is small, then the asymptotic complexity does not matter as much
        % as the constant factors.
        NumAllocatedB =< 5
    ;
        % Avoid flattening NamesA if it does not buy us enough speed on NamesB.
        NumAllocatedA >= NumAllocatedB * 16
    ).

:- pred use_simple_loop_without_names(int::in, int::in) is semidet.
:- pragma inline(pred(use_simple_loop_without_names/2)).

use_simple_loop_without_names(_NumAllocatedA, NumAllocatedB) :-
    % XXX The numbers in this heuristic should to be determined by benchmarks.
    % If NumAllocatedB (the N in the complexity analysis above)
    % is small, then the asymptotic complexity does not matter as much
    % as the constant factors.
    NumAllocatedB =< 5.

%---------------------%

merge_renaming(VarSetA, VarSetB, VarSet, Renaming) :-
    VarSetA = varset(SupplyA, NamesA, ValuesA),
    VarSetB = varset(SupplyB, NamesB, _ValuesB),
    NumAllocatedA = var_supply_num_allocated(SupplyA),
    NumAllocatedB = var_supply_num_allocated(SupplyB),
    ( if use_simple_loop(NumAllocatedA, NumAllocatedB) then
        term.init_var_supply(SupplyB0),
        map.init(Renaming0),
        simple_merge_renaming_loop(SupplyB0, SupplyB, NamesB,
            SupplyA, Supply, NamesA, Names, Renaming0, Renaming)
    else
        map.to_sorted_assoc_list(NamesA, NamesListA),
        map.to_sorted_assoc_list(NamesB, NamesListB),
        fast_merge_renaming_loop(1, NumAllocatedB, NumAllocatedA, NumAllocated,
            NamesListB, [], RevNamesListSuffix, [], RevRenamingList),
        Supply = force_construct_var_supply(NumAllocated),
        list.reverse(RevNamesListSuffix, NamesListSuffix),
        NamesList = NamesListA ++ NamesListSuffix,
        map.from_sorted_assoc_list(NamesList, Names),
        map.from_rev_sorted_assoc_list(RevRenamingList, Renaming)
    ),
    VarSet = varset(Supply, Names, ValuesA).

:- pred simple_merge_renaming_loop(var_supply(T)::in, var_supply(T)::in,
    map(var(T), string)::in,
    var_supply(T)::in, var_supply(T)::out,
    map(var(T), string)::in, map(var(T), string)::out,
    renaming(T)::in, renaming(T)::out) is det.

simple_merge_renaming_loop(!.SupplyB, MaxSupplyB, NamesB,
        !Supply, !Names, !Renaming) :-
    ( if !.SupplyB = MaxSupplyB then
        true
    else
        term.create_var(Var, !Supply),
        term.create_var(VarB, !SupplyB),
        ( if map.search(NamesB, VarB, NameB) then
            map.det_insert(Var, NameB, !Names)
        else
            true
        ),
        map.det_insert(VarB, Var, !Renaming),
        disable_warning [suspicious_recursion] (
            simple_merge_renaming_loop(!.SupplyB, MaxSupplyB, NamesB,
                !Supply, !Names, !Renaming)
        )
    ).

:- pred fast_merge_renaming_loop(int::in, int::in, int::in, int::out,
    assoc_list(var(T), string)::in,
    assoc_list(var(T), string)::in, assoc_list(var(T), string)::out,
    assoc_list(var(T), var(T))::in, assoc_list(var(T), var(T))::out) is det.

fast_merge_renaming_loop(CurVarNumB, NumAllocatedB, !NumAllocated,
        VarNamesB, !RevNamesListSuffix, !RevRenaming) :-
    (
        VarNamesB = [],
        fast_merge_renaming_loop_leftover(CurVarNumB, NumAllocatedB,
            !NumAllocated, !RevRenaming)
    ;
        VarNamesB = [HeadVarB - HeadNameB | TailVarNamesB],
        !:NumAllocated = !.NumAllocated + 1,
        Var = force_construct_var(!.NumAllocated),
        VarB = force_construct_var(CurVarNumB),
        !:RevRenaming = [VarB - Var | !.RevRenaming],
        ( if CurVarNumB = var_to_int(HeadVarB) then
            !:RevNamesListSuffix = [Var - HeadNameB | !.RevNamesListSuffix],
            NextVarNamesB = TailVarNamesB
        else
            NextVarNamesB = VarNamesB
        ),
        fast_merge_renaming_loop(CurVarNumB + 1, NumAllocatedB, !NumAllocated,
            NextVarNamesB, !RevNamesListSuffix, !RevRenaming)
    ).

    % A version of fast_merge_renaming_loop specialized for the case
    % where there are no names left from VarSetB.
    %
:- pred fast_merge_renaming_loop_leftover(int::in, int::in, int::in, int::out,
    assoc_list(var(T), var(T))::in, assoc_list(var(T), var(T))::out) is det.

fast_merge_renaming_loop_leftover(CurVarNumB, NumAllocatedB,
        !NumAllocated, !RevRenaming) :-
    ( if CurVarNumB > NumAllocatedB then
        true
    else
        !:NumAllocated = !.NumAllocated + 1,
        Var = force_construct_var(!.NumAllocated),
        VarB = force_construct_var(CurVarNumB),
        !:RevRenaming = [VarB - Var | !.RevRenaming],
        fast_merge_renaming_loop_leftover(CurVarNumB + 1, NumAllocatedB,
            !NumAllocated, !RevRenaming)
    ).

%---------------------%

merge_renaming_without_names(VarSetA, VarSetB, VarSet, Renaming) :-
    VarSetA = varset(SupplyA, NamesA, ValuesA),
    VarSetB = varset(SupplyB, _NamesB, _ValuesB),
    NumAllocatedA = var_supply_num_allocated(SupplyA),
    NumAllocatedB = var_supply_num_allocated(SupplyB),
    ( if use_simple_loop_without_names(NumAllocatedA, NumAllocatedB) then
        term.init_var_supply(SupplyB0),
        map.init(Renaming0),
        simple_merge_renaming_without_names_loop(SupplyB0, SupplyB,
            SupplyA, Supply, Renaming0, Renaming)
    else
        fast_merge_renaming_without_names_loop(1, NumAllocatedB,
            NumAllocatedA, NumAllocated, [], RevRenamingList),
        Supply = force_construct_var_supply(NumAllocated),
        map.from_rev_sorted_assoc_list(RevRenamingList, Renaming)
    ),
    VarSet = varset(Supply, NamesA, ValuesA).

:- pred simple_merge_renaming_without_names_loop(var_supply(T)::in,
    var_supply(T)::in, var_supply(T)::in, var_supply(T)::out,
    renaming(T)::in, renaming(T)::out) is det.

simple_merge_renaming_without_names_loop(!.SupplyB, MaxSupplyB,
        !Supply, !Renaming) :-
    ( if !.SupplyB = MaxSupplyB then
        true
    else
        term.create_var(Var, !Supply),
        term.create_var(VarB, !SupplyB),
        map.det_insert(VarB, Var, !Renaming),
        disable_warning [suspicious_recursion] (
            simple_merge_renaming_without_names_loop(!.SupplyB, MaxSupplyB,
                !Supply, !Renaming)
        )
    ).

:- pred fast_merge_renaming_without_names_loop(int::in, int::in,
    int::in, int::out,
    assoc_list(var(T), var(T))::in, assoc_list(var(T), var(T))::out) is det.

fast_merge_renaming_without_names_loop(CurVarNumB, NumAllocatedB,
        !NumAllocated, !RevRenaming) :-
    ( if CurVarNumB > NumAllocatedB then
        true
    else
        !:NumAllocated = !.NumAllocated + 1,
        Var = force_construct_var(!.NumAllocated),
        VarB = force_construct_var(CurVarNumB),
        !:RevRenaming = [VarB - Var | !.RevRenaming],
        fast_merge_renaming_without_names_loop(CurVarNumB + 1, NumAllocatedB,
            !NumAllocated, !RevRenaming)
    ).

%---------------------------------------------------------------------------%

merge(VarSetA, VarSetB, TermList0, VarSet, TermList) :-
    varset.merge_renaming(VarSetA, VarSetB, VarSet, Renaming),
    term_subst.apply_renaming_in_terms(Renaming, TermList0, TermList).

merge_without_names(VarSetA, VarSetB, TermList0, VarSet, TermList) :-
    varset.merge_renaming_without_names(VarSetA, VarSetB, VarSet, Renaming),
    term_subst.apply_renaming_in_terms(Renaming, TermList0, TermList).

%---------------------------------------------------------------------------%

create_name_var_map(VarSet) = NameVars :-
    varset.create_name_var_map(VarSet, NameVars).

create_name_var_map(VarSet, NameVars) :-
    VarNames = VarSet ^ var_names,
    map.keys(VarNames, Vars),
    map.values(VarNames, Names),
    map.from_corresponding_lists(Names, Vars, NameVars).

%---------------------------------------------------------------------------%

var_name_list(VarSet) = VarNameList :-
    varset.var_name_list(VarSet, VarNameList).

var_name_list(VarSet, VarNameList) :-
    VarNames = VarSet ^ var_names,
    map.to_assoc_list(VarNames, VarNameList).

%---------------------------------------------------------------------------%

ensure_unique_names(AllVars, Suffix, !.VarSet) = !:VarSet :-
    varset.ensure_unique_names(AllVars, Suffix, !VarSet).

ensure_unique_names(AllVars, Suffix, !VarSet) :-
    VarNames0 = !.VarSet ^ var_names,
    ( if Suffix = "" then
        unexpected($pred,
            "Error: the disambiguating suffix is the empty string.")
    else
        varset.ensure_unique_names_loop(AllVars, Suffix, set.init, VarNames0,
            map.init, VarNames),
        !VarSet ^ var_names := VarNames
    ).

:- pred ensure_unique_names_loop(list(var(T))::in, string::in,
    set(string)::in, map(var(T), string)::in, map(var(T), string)::in,
    map(var(T), string)::out) is det.

ensure_unique_names_loop([], _, _, _, !VarNames).
ensure_unique_names_loop([Var | Vars], Suffix, !.UsedNames,
        OldVarNames, !VarNames) :-
    ( if map.search(OldVarNames, Var, OldName) then
        ( if set.member(OldName, !.UsedNames) then
            term.var_to_int(Var, VarNum),
            TrialName = OldName ++ "_" ++ string.int_to_string(VarNum)
        else
            TrialName = OldName
        )
    else
        term.var_to_int(Var, VarNum),
        TrialName = "Var_" ++ string.int_to_string(VarNum)
    ),
    append_suffix_until_unique(TrialName, Suffix, !.UsedNames, FinalName),
    set.insert(FinalName, !UsedNames),
    map.det_insert(Var, FinalName, !VarNames),
    ensure_unique_names_loop(Vars, Suffix, !.UsedNames,
        OldVarNames, !VarNames).

:- pred append_suffix_until_unique(string::in, string::in, set(string)::in,
    string::out) is det.

append_suffix_until_unique(Trial0, Suffix, UsedNames, Final) :-
    ( if set.member(Trial0, UsedNames) then
        string.append(Trial0, Suffix, Trial1),
        append_suffix_until_unique(Trial1, Suffix, UsedNames, Final)
    else
        Final = Trial0
    ).

%---------------------------------------------------------------------------%

undo_default_names(VarSet0, VarSet) :-
    VarSet0 = varset(Supply0, Names0, Values0),
    map.to_sorted_assoc_list(Names0, NamesList0),
    undo_default_names_loop(NamesList0, [], RevNamesList),
    map.from_rev_sorted_assoc_list(RevNamesList, Names),
    VarSet = varset(Supply0, Names, Values0).

:- pred undo_default_names_loop(assoc_list(var(T), string)::in,
    assoc_list(var(T), string)::in, assoc_list(var(T), string)::out) is det.

undo_default_names_loop([], !RevNamesList).
undo_default_names_loop([Var - Name | VarNames], !RevNamesList) :-
    ( if
        % Does the variable name have the default form used by lookup_name?
        remove_prefix("V_", Name, NameAfterV),
        string.to_int(NameAfterV, _IntAfterV)
    then
        % Yes, so delete the name by simply not adding it to the list.
        true
    else
        !:RevNamesList = [Var - Name | !.RevNamesList]
    ),
    undo_default_names_loop(VarNames, !RevNamesList).

%---------------------------------------------------------------------------%

select(!.VarSet, Vars) = !:VarSet :-
    varset.select(Vars, !VarSet).

select(Vars, !VarSet) :-
    !.VarSet = varset(Supply, VarNameMap0, Values0),
    map.select(VarNameMap0, Vars, VarNameMap),
    map.select(Values0, Vars, Values),
    !:VarSet = varset(Supply, VarNameMap, Values).

%---------------------------------------------------------------------------%

squash(OldVarSet, KeptVars, NewVarSet, Subst) :-
    % Create a new varset with the same number of variables.
    list.length(KeptVars, NumVars),
    varset.init(NewVarSet0),
    varset.new_vars(NumVars, NewVars0, NewVarSet0, NewVarSet1),

    % We need to sort the fresh variables, to ensure that the substitution
    % that we create below does not alter the relative ordering of the
    % variables.
    list.sort(NewVars0, NewVars),

    % Copy the variable names across from the old varset to the new varset.
    varset.var_name_list(OldVarSet, VarNames),
    map.from_corresponding_lists(KeptVars, NewVars, Subst),
    copy_var_names(VarNames, Subst, NewVarSet1, NewVarSet).

:- pred copy_var_names(assoc_list(var(T), string)::in, renaming(T)::in,
    varset(T)::in, varset(T)::out) is det.

copy_var_names([], _Subst, !NewVarSet).
copy_var_names([OldVar - Name | Rest], Subst, !NewVarSet) :-
    ( if map.search(Subst, OldVar, NewVar) then
        varset.name_var(NewVar, Name, !NewVarSet)
    else
        true
    ),
    copy_var_names(Rest, Subst, !NewVarSet).

%---------------------------------------------------------------------------%

coerce(!.VarSet) = !:VarSet :-
    varset.coerce(!VarSet).

coerce(!VarSet) :-
    % Normally calls to this predicate should only be generated by the
    % compiler, but type coercion by copying was taking about 3% of the
    % compiler's runtime.
    private_builtin.unsafe_type_cast(!VarSet).

%---------------------------------------------------------------------------%

max_var(varset(VarSupply, _, _)) = term.var_supply_max_var(VarSupply).

num_allocated(varset(VarSupply, _, _)) =
    term.var_supply_num_allocated(VarSupply).

get_var_names(VarSet, VarNameMap) :-
    VarSet = varset(_, VarNameMap, _).

construct_varset(N, VarNameMap, VarSet) :-
    VarSupply = force_construct_var_supply(N),
    map.init(VarValueMap),
    VarSet = varset(VarSupply, VarNameMap, VarValueMap).

%---------------------------------------------------------------------------%
:- end_module varset.
%---------------------------------------------------------------------------%
