Files
mercury/compiler/pre_quantification.m
Zoltan Somogyi 0c162fb50e Improve diagnostics for higher-order mode errors.
In case of a problem with an argument of a higher order call,
we used to generate diagnostics containing text such as

    in argument 2 (i.e. argument 1 of the called function)
    of impure higher-order function call:

This clumsy construction was needed because the HLDS did not contain
enough info. Specifically, it did not specify whether in the source code,
the higher order call was written as e.g. call(P, A, B, C) or as P(A, B, C).
If the former, then then a problem with e.g. A occurs in argument 2
of the call to the 'call' builtin; if the latter, then it occurs
in argument 1 of the call to 'P'.

This diff fixes that. It adds to the HLDS representation of higher order
calls a record of the original form of the call in the source code

compiler/hlds_goal.m:
    Add this field, the higher_order_syntax field, to the representation
    of higher order calls.

compiler/goal_expr_to_goal.m:
    Fill the higher_order_syntax field for higher order predicate calls.

compiler/resolve_unify_functor.m:
    Fill the higher_order_syntax field for higher order function calls.

compiler/hlds_pred.m:
    Change the representation of generic calls in call_ids, which we use
    to identify callees for diagnostics, to include both the variable
    specifying the callee in higher order calls (by changing from a
    generic_call_id to the generic_call from which it would be derived
    by throwing out that info), and the var_name_source needed to
    look up its name.

compiler/hlds_out_util.m:
    Use the new higher_order_syntax field to generate text that

    - identifies the higher order call as using either call(P, A, B, C)
      syntax or P(A, B, C) syntax, and likewise for function, and

    - specifies the exact argument number (which will depend on the above
      distinction).

    This should result in more easily understandable diagnostics.

    Add a version of this predicate that generates text that specifically
    describes the higher order callee, not the higher order call as a whole.

    Add an option to suppress the printing of variable names.
    We use this to *not* refer to e.g. "the predicate P" when reporting
    an error about P actually being a function, not a predicate.

compiler/mode_info.m:
    Rename mode_context_call to mode_context_call_arg, because
    the context it describes is one argument of a call.

    Add a new mode context, mode_context_call, which now describes
    a call as a whole.

compiler/mode_errors.m:
    Use the new facilies described above to generate better diagnostics
    for higher order calls.

compiler/modecheck_util.m:
    Document the reason why we handle argument numbers for higher order calls
    the way we do.

    When generating an error that relates the whole of a higher order call,
    use the new mechanism in mode_info.m to record the fact that the error
    is not specific to any one argument of the call. Not doing this would
    include the argument number in the context of the error, which would be
    misleading.

    Add a utility function that is needed by more than one module below.

compiler/*.m:
    Conform to the changes above.

tests/invalid/anys_in_negated_contexts.err_exp:
tests/invalid/bind_var_errors.err_exp:
tests/invalid/constrained_poly_insts_2.err_exp:
tests/invalid/det_errors_cc.err_exp:
tests/invalid/fbnf.err_exp:
tests/invalid/functor_ho_inst_bad_2.err_exp:
tests/invalid/higher_order_mode_mismatch.err_exp:
tests/invalid/ho_any_inst.err_exp:
tests/invalid/ho_type_mode_bug.err_exp:
tests/invalid/ho_unique_error.err_exp:
tests/invalid/mode_error_arg_number_ho.err_exp:
tests/invalid/no_ho_inst.err_exp:
tests/invalid/type_diff.err_exp:
tests/invalid_purity/impure_func_t5_fixed.err_exp:
tests/invalid_purity/impure_pred_t1_fixed.err_exp:
tests/invalid_purity/impure_pred_t2.err_exp:
tests/invalid_purity/purity_nonsense_1.err_exp:
tests/invalid_purity/purity_nonsense_2.err_exp:
    Expect updated error messages.
2024-11-01 13:56:55 +11:00

503 lines
21 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2017, 2020-2024 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%
%
% File: pre_quantification.m.
% Main author: zs.
%
% This module adds implicit quantifications of some variables just inside
% trace goal scopes in order to avoid a minor but annoying problem, which can
% also be confusing for people who come across it the first time.
%
% Consider the following code:
%
% :- pred p(int::in, int::in, int::out) is det.
%
% p(A, B, X) :-
% trace [io(!IO)] (
% ToPrint = A,
% io.format("A = %d\n", [i(ToPrint)], !IO)
% ),
% trace [io(!IO)] (
% ToPrint = B,
% io.format("B = %d\n", [i(ToPrint)], !IO)
% ),
% X = A + B,
% trace [io(!IO)] (
% ToPrint = X,
% io.format("X = %d\n", [i(ToPrint)], !IO)
% ).
%
% Since the variable ToPrint is used inside more than one trace goal,
% the compiler's usual scope rules consider it to be a nonlocal variable
% in each trace goal. This is a problem, because the compiler does not allow
% trace goals to bind variables that are not local to the trace goal.
% It does this to preserve the ability of the program to work whether or not
% the trace goal is there, since it can be deleted if either its compile_time
% or run_time condition turns out to be false.
%
% However, in cases like this, where the nonlocal variable occurs *only*
% inside trace goals, leaving ToPrint unbound if a trace goal is deleted
% will not affect the execution of the program. Having it bound more than once
% if the trace goals *aren't* deleted would be a problem, but this is easily
% fixed by making ToPrint existentially quantified inside each trace goal;
% that way, each trace goal will bind and use its own copy of ToPrint.
%
% The job of this module is to find out which variables in a clause body
% are used only inside trace goals, and to insert the quantifications
% into trace goals as necessary to ensure that all such variables
% will end up being local to each trace goal scope. The part of the compiler
% that ensures this is the quantification pass, which is invoked on the
% goal we generate pretty much immediately after we return it.
%
% The overall effect of putting this pass before the quantification pass
% is a minor tweak of the usual rules of quantification, a tweak that
% makes the rules enforced by the compiler resemble the rules expected
% by programmers more closely, helping the compiler approach closer to the
% principle of least astonishment.
%
%-----------------------------------------------------------------------------%
:- module hlds.pre_quantification.
:- interface.
:- import_module hlds.hlds_goal.
%-----------------------------------------------------------------------------%
:- pred separate_trace_goal_only_locals(hlds_goal::in, hlds_goal::out) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.
:- import_module assoc_list.
:- import_module counter.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module sparse_bitset.
:- import_module uint.
%-----------------------------------------------------------------------------%
%
% The pre-quantification algorithm is based on dividing the clause body
% into one or more zones, each identified by a non-negative integer.
% The zones are based on viewing the clause body as a tree:
%
% - with each goal being a node,
% - atomic goals being leaf nodes, and
% - each compound goal having a subtree for each subgoal.
%
% If lambda goals didn't exist, each zone would be identified by its top node.
%
% The top node of zone 0 is the root of the tree, while the top node of
% every other zone is a trace goal, i.e. a scope goal whose scope_reason
% is trace_goal. Every trace goal is the top node of a zone, with the
% exception of trace goals that are contained inside other trace goals.
% (We have no rule against nesting trace goals, though they don't make
% much sense, and I (zs) have never seen such nesting.)
%
% The zone that each node belongs to is the zone whose top node you first find
% when you traverse the tree upwards from the node. In other words, each zone
% claims the nodes beneath it, except the ones that are claimed by another
% zone whose top node is lower down.
%
% Since lambda goals do exist, we have to handle them. We do so by considering
% the lambda goal to be effectively a separate predicate body inside this
% clause (since the compiler will eventually turn it into that). So the
% top level of a rhs_lambda_goal is also considered to be in zone 0.
:- type zone == uint.
:- func top_zone = zone.
:- pragma inline(func(top_zone/0)).
top_zone = 0u.
%-----------------------------------------------------------------------------%
%
% The pre-quantification algorithm has three main steps.
%
% - The first step is to find out which variables are used in which zones.
% This is done by build_vars_to_zones_in_*, which build the VarsToZones map.
%
% - The second step is finding out which variables occur only inside
% trace goals, not outside them. These variables should be local to the
% trace goals they appear in. If they occur in only one trace goal,
% ordinary quantification will ensure this. We need to add explicit
% existential quantifications for them only if they occur inside
% more than one trace goal. In terms of zones, this means variables
% that don't occur in zone 0 but do appear in more than one other zone.
% (We call these variables the "duplicated" variables.) The job of
% build_zones_to_dup_vars is to map each trace goal zone to the list
% of variables that needs to have an existential quantifier added for it
% in the scope of that trace goal, and to return that as ZonesToDupVars.
%
% - The third step is to go through the clause body and insert those
% existential quantification scopes inside every trace goal scope
% that contains one or more of these "duplicated" variables.
%
% Our parent ensures that we don't get called on clauses that
% don't contain any trace goals. Most clauses that have trace goals
% will have only a few of them, so the sparse_bitset(zone) will
% typically contain only a single node.
%
:- type vars_to_zones == map(prog_var, sparse_bitset(zone)).
:- type zones_to_dup_vars == map(zone, list(prog_var)).
separate_trace_goal_only_locals(Goal0, Goal) :-
map.init(VarsToZones0),
counter.uinit(1u, TraceCounter0),
build_vars_to_zones_in_goal(top_zone, Goal0, TraceCounter0, _,
VarsToZones0, VarsToZones),
map.init(ZonesToDupVars0),
map.foldl(build_zones_to_dup_vars, VarsToZones,
ZonesToDupVars0, ZonesToDupVars),
( if map.is_empty(ZonesToDupVars) then
Goal = Goal0
else
add_exist_scopes_for_dup_vars_in_goal(top_zone, ZonesToDupVars,
Goal0, Goal, TraceCounter0, _)
).
%-----------------------------------------------------------------------------%
% The first step described in the comment above
% separate_trace_goal_only_locals.
%
:- pred build_vars_to_zones_in_goal(zone::in, hlds_goal::in,
ucounter::in, ucounter::out, vars_to_zones::in, vars_to_zones::out) is det.
build_vars_to_zones_in_goal(CurZone, Goal, !TraceCounter, !VarsToZones) :-
Goal = hlds_goal(GoalExpr, _GoalInfo),
(
GoalExpr = plain_call(_PredId, _ProcId, ArgVars, _Builtin,
_MaybeUnifyContext, _SymName),
record_vars_in_zone(CurZone, ArgVars, !VarsToZones)
;
GoalExpr = generic_call(GenericCall, ArgVars, _Modes,
_RegTypes, _Detism),
record_vars_in_zone(CurZone, ArgVars, !VarsToZones),
(
GenericCall = higher_order(ClosureVar, _Purity, _CallKind, _Arity,
_Syntax),
record_var_in_zone(CurZone, ClosureVar, !VarsToZones)
;
GenericCall = class_method(TypeClassInfoVar, _MethodNum,
_ClassId, _PFSymNameArity),
record_var_in_zone(CurZone, TypeClassInfoVar, !VarsToZones)
;
GenericCall = event_call(_)
;
GenericCall = cast(_)
)
;
GoalExpr = call_foreign_proc(_Attrs, _PredId, _ProcId,
ForeignArgs, ExtraArgs, _TraceCond, _Impl),
record_vars_in_zone(CurZone, list.map(foreign_arg_var, ForeignArgs),
!VarsToZones),
record_vars_in_zone(CurZone, list.map(foreign_arg_var, ExtraArgs),
!VarsToZones)
;
GoalExpr = unify(LHSVar, RHS, _Mode, _Kind, _Context),
record_var_in_zone(CurZone, LHSVar, !VarsToZones),
(
RHS = rhs_var(RHSVar),
record_var_in_zone(CurZone, RHSVar, !VarsToZones)
;
RHS = rhs_functor(_ConsId, _IsExistConstr, RHSArgVars),
record_vars_in_zone(CurZone, RHSArgVars, !VarsToZones)
;
RHS = rhs_lambda_goal(_Purity, _Groundness, _PredOrFunc,
_NonLocals, LambdaVarsModes, _Detism, LambdaGoal),
% The lambda goal is the one clause of the procedure that
% we will construct from LambdaGoal. We therefore treat it
% as we treat the top level goal.
assoc_list.keys(LambdaVarsModes, LambdaVars),
LambdaZone = top_zone,
record_vars_in_zone(LambdaZone, LambdaVars, !VarsToZones),
build_vars_to_zones_in_goal(LambdaZone, LambdaGoal,
!TraceCounter, !VarsToZones)
)
;
( GoalExpr = conj(_ConjType, SubGoals)
; GoalExpr = disj(SubGoals)
),
list.foldl2(build_vars_to_zones_in_goal(CurZone), SubGoals,
!TraceCounter, !VarsToZones)
;
GoalExpr = switch(Var, _CanFail, Cases),
record_var_in_zone(CurZone, Var, !VarsToZones),
list.foldl2(build_vars_to_zones_in_case(CurZone), Cases,
!TraceCounter, !VarsToZones)
;
GoalExpr = negation(SubGoal),
build_vars_to_zones_in_goal(CurZone, SubGoal,
!TraceCounter, !VarsToZones)
;
GoalExpr = scope(Reason, SubGoal),
( if
Reason = trace_goal(_, _, _, _, _),
CurZone = top_zone
then
counter.uallocate(NewZone, !TraceCounter),
build_vars_to_zones_in_goal(NewZone, SubGoal,
!TraceCounter, !VarsToZones)
else
build_vars_to_zones_in_goal(CurZone, SubGoal,
!TraceCounter, !VarsToZones)
)
;
GoalExpr = if_then_else(Vars, Cond, Then, Else),
record_vars_in_zone(CurZone, Vars, !VarsToZones),
build_vars_to_zones_in_goal(CurZone, Cond,
!TraceCounter, !VarsToZones),
build_vars_to_zones_in_goal(CurZone, Then,
!TraceCounter, !VarsToZones),
build_vars_to_zones_in_goal(CurZone, Else,
!TraceCounter, !VarsToZones)
;
GoalExpr = shorthand(ShortHand),
(
ShortHand = bi_implication(SubGoalA, SubGoalB),
build_vars_to_zones_in_goal(CurZone, SubGoalA,
!TraceCounter, !VarsToZones),
build_vars_to_zones_in_goal(CurZone, SubGoalB,
!TraceCounter, !VarsToZones)
;
ShortHand = atomic_goal(_GoalType, OuterVars, InnerVars,
MaybeOutputVars, MainGoal, OrElseGoals, _OrElseInners),
OuterVars = atomic_interface_vars(OVA, OVB),
InnerVars = atomic_interface_vars(IVA, IVB),
record_vars_in_zone(CurZone, [OVA, OVB, IVA, IVB], !VarsToZones),
(
MaybeOutputVars = no
;
MaybeOutputVars = yes(OutputVars),
record_vars_in_zone(CurZone, OutputVars, !VarsToZones)
),
build_vars_to_zones_in_goal(CurZone, MainGoal,
!TraceCounter, !VarsToZones),
list.foldl2(build_vars_to_zones_in_goal(CurZone), OrElseGoals,
!TraceCounter, !VarsToZones)
;
ShortHand = try_goal(MaybeIOStateVars, ResultVar, SubGoal),
(
MaybeIOStateVars = no
;
MaybeIOStateVars = yes(IOStateVars),
IOStateVars = try_io_state_vars(ISVA, ISVB),
record_vars_in_zone(CurZone, [ISVA, ISVB], !VarsToZones)
),
record_var_in_zone(CurZone, ResultVar, !VarsToZones),
build_vars_to_zones_in_goal(CurZone, SubGoal,
!TraceCounter, !VarsToZones)
)
).
:- pred build_vars_to_zones_in_case(zone::in, case::in,
ucounter::in, ucounter::out, vars_to_zones::in, vars_to_zones::out) is det.
build_vars_to_zones_in_case(CurZone, Case, !TraceCounter, !VarsToZones) :-
Case = case(_MainConsId, _OtherConsIds, Goal),
build_vars_to_zones_in_goal(CurZone, Goal, !TraceCounter, !VarsToZones).
:- pred record_vars_in_zone(zone::in, list(prog_var)::in,
vars_to_zones::in, vars_to_zones::out) is det.
record_vars_in_zone(_Zone, [], !VarsToZones).
record_vars_in_zone(Zone, [Var | Vars], !VarsToZones) :-
record_var_in_zone(Zone, Var, !VarsToZones),
record_vars_in_zone(Zone, Vars, !VarsToZones).
:- pred record_var_in_zone(zone::in, prog_var::in,
vars_to_zones::in, vars_to_zones::out) is det.
record_var_in_zone(Zone, Var, !VarsToZones) :-
( if map.search(!.VarsToZones, Var, Zones0) then
% Most variables will occur only in one zone, typically zone 0.
% However, they will typically occur there more than once.
% We don't want to set the bit of the zone more than once.
( if sparse_bitset.insert_new(Zone, Zones0, Zones1) then
map.det_update(Var, Zones1, !VarsToZones)
else
% Zone is already in Zones0.
true
)
else
Zones = sparse_bitset.make_singleton_set(Zone),
map.det_insert(Var, Zones, !VarsToZones)
).
%-----------------------------------------------------------------------------%
% The second step described in the comment above
% separate_trace_goal_only_locals.
%
:- pred build_zones_to_dup_vars(prog_var::in, sparse_bitset(zone)::in,
zones_to_dup_vars::in, zones_to_dup_vars::out) is det.
build_zones_to_dup_vars(Var, Zones, !ZonesToDupVars) :-
ZoneList = sparse_bitset.to_sorted_list(Zones),
( if
ZoneList = [FirstZone, _SecondZone | _],
FirstZone \= top_zone
then
list.foldl(add_var_to_zone(Var), ZoneList, !ZonesToDupVars)
else
true
).
:- pred add_var_to_zone(prog_var::in, zone::in,
zones_to_dup_vars::in, zones_to_dup_vars::out) is det.
add_var_to_zone(Var, Zone, !ZonesToDupVars) :-
( if map.search(!.ZonesToDupVars, Zone, DupVars0) then
DupVars = [Var | DupVars0],
map.det_update(Zone, DupVars, !ZonesToDupVars)
else
map.det_insert(Zone, [Var], !ZonesToDupVars)
).
%-----------------------------------------------------------------------------%
% The third step described in the comment above
% separate_trace_goal_only_locals.
%
:- pred add_exist_scopes_for_dup_vars_in_goal(zone::in, zones_to_dup_vars::in,
hlds_goal::in, hlds_goal::out, ucounter::in, ucounter::out) is det.
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
!Goal, !TraceCounter) :-
!.Goal = hlds_goal(GoalExpr0, GoalInfo),
(
( GoalExpr0 = plain_call(_, _, _, _, _, _)
; GoalExpr0 = generic_call(_, _, _, _, _)
; GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _)
),
GoalExpr = GoalExpr0
;
GoalExpr0 = unify(LHSVar, RHS0, Mode, Kind, Context),
(
( RHS0 = rhs_var(_)
; RHS0 = rhs_functor(_, _, _)
),
GoalExpr = GoalExpr0
;
RHS0 = rhs_lambda_goal(Purity, Groundness, PredOrFunc,
NonLocals, LambdaVarsModes, Detism, LambdaGoal0),
LambdaZone = top_zone,
add_exist_scopes_for_dup_vars_in_goal(LambdaZone, ZonesToDupVars,
LambdaGoal0, LambdaGoal, !TraceCounter),
RHS = rhs_lambda_goal(Purity, Groundness, PredOrFunc,
NonLocals, LambdaVarsModes, Detism, LambdaGoal),
GoalExpr = unify(LHSVar, RHS, Mode, Kind, Context)
)
;
GoalExpr0 = conj(ConjType, SubGoals0),
list.map_foldl(
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars),
SubGoals0, SubGoals, !TraceCounter),
GoalExpr = conj(ConjType, SubGoals)
;
GoalExpr0 = disj(SubGoals0),
list.map_foldl(
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars),
SubGoals0, SubGoals, !TraceCounter),
GoalExpr = disj(SubGoals)
;
GoalExpr0 = switch(Var, CanFail, Cases0),
list.map_foldl(
add_exist_scopes_for_dup_vars_in_case(CurZone, ZonesToDupVars),
Cases0, Cases, !TraceCounter),
GoalExpr = switch(Var, CanFail, Cases)
;
GoalExpr0 = negation(SubGoal0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
SubGoal0, SubGoal, !TraceCounter),
GoalExpr = negation(SubGoal)
;
GoalExpr0 = scope(Reason, SubGoal0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
SubGoal0, SubGoal1, !TraceCounter),
( if
Reason = trace_goal(_, _, _, _, _),
CurZone = top_zone
then
counter.uallocate(NewZone, !TraceCounter),
( if map.search(ZonesToDupVars, NewZone, DupVars) then
list.sort(DupVars, SortedDupVars),
QuantReason = exist_quant(SortedDupVars, compiler_quant),
QuantExpr = scope(QuantReason, SubGoal1),
SubGoal1 = hlds_goal(_, SubGoalInfo1),
SubGoal = hlds_goal(QuantExpr, SubGoalInfo1)
else
SubGoal = SubGoal1
)
else
SubGoal = SubGoal1
),
GoalExpr = scope(Reason, SubGoal)
;
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
Cond0, Cond, !TraceCounter),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
Then0, Then, !TraceCounter),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
Else0, Else, !TraceCounter),
GoalExpr = if_then_else(Vars, Cond, Then, Else)
;
GoalExpr0 = shorthand(ShortHand0),
(
ShortHand0 = bi_implication(SubGoalA0, SubGoalB0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
SubGoalA0, SubGoalA, !TraceCounter),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
SubGoalB0, SubGoalB, !TraceCounter),
ShortHand = bi_implication(SubGoalA, SubGoalB)
;
ShortHand0 = atomic_goal(GoalType, OuterVars, InnerVars,
MaybeOutputVars, MainGoal0, OrElseGoals0, OrElseInners),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
MainGoal0, MainGoal, !TraceCounter),
list.map_foldl(
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars),
OrElseGoals0, OrElseGoals, !TraceCounter),
ShortHand = atomic_goal(GoalType, OuterVars, InnerVars,
MaybeOutputVars, MainGoal, OrElseGoals, OrElseInners)
;
ShortHand0 = try_goal(MaybeIOStateVars, ResultVar, SubGoal0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
SubGoal0, SubGoal, !TraceCounter),
ShortHand = try_goal(MaybeIOStateVars, ResultVar, SubGoal)
),
GoalExpr = shorthand(ShortHand)
),
!:Goal = hlds_goal(GoalExpr, GoalInfo).
:- pred add_exist_scopes_for_dup_vars_in_case(zone::in, zones_to_dup_vars::in,
case::in, case::out, ucounter::in, ucounter::out) is det.
add_exist_scopes_for_dup_vars_in_case(CurZone, ZonesToDupVars,
!Case, !TraceCounter) :-
!.Case = case(MainConsId, OtherConsIds, Goal0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
Goal0, Goal, !TraceCounter),
!:Case = case(MainConsId, OtherConsIds, Goal).
%-----------------------------------------------------------------------------%
:- end_module hlds.pre_quantification.
%-----------------------------------------------------------------------------%