%---------------------------------------------------------------------------%
% vim: ts=4 sw=4 et ft=mercury
%---------------------------------------------------------------------------%
%
% Test the float_reg.m pass.

:- module ho_float_reg.
:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is cc_multi.

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

:- implementation.

:- import_module float.
:- import_module list.
:- import_module pair.
:- import_module require.
:- import_module solutions.

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

main(!IO) :-
    P1 = foo(1.5),
    P2 = foo(1.5, 2.5),

    begin("plain call", !IO),
    some [Res] (
        foo(1.5, 2.5, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    begin("higher-order call", !IO),
    some [Res] (
        P1(2.5, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    begin("docall", !IO),
    some [Res] (
        docall(P2, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    begin("docall_inline", !IO),
    some [Res] (
        docall_inline(P2, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    begin("get_docall", !IO),
    some [DoCall, Res] (
        get_docall(DoCall),
        DoCall(P2, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    begin("get_docall2", !IO),
    some [DoCall1, DoCall2, Res] (
        get_docall2(DoCall2),
        DoCall2(DoCall1),
        DoCall1(P2, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    begin("get_docall3", !IO),
    some [DoCall1, DoCall2, DoCall3, Res] (
        get_docall3(DoCall3),
        DoCall3(DoCall2),
        DoCall2(DoCall1),
        DoCall1(P2, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    begin("docall_foreign_poly", !IO),
    some [Res] (
        docall_foreign_poly(P2, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    begin("docall_foreign_float", !IO),
    some [Res] (
        docall_foreign_float(P2, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    some [SPoly, SMono, Res1, Res2, Res3] (
        SPoly = struct_poly(P2),
        SMono = struct_mono(foo(1.5, 2.5)) : struct(float),

        begin("docall_struct_poly_generic_args", !IO),
        docall_struct_poly_generic_args(SPoly, 3.5, Res1),
        io.write_float(Res1, !IO),
        io.nl(!IO),

        begin("docall_struct_poly_float_args", !IO),
        docall_struct_poly_float_args(SPoly, 3.5, Res2),
        io.write_float(Res2, !IO),
        io.nl(!IO),

        begin("docall_struct_mono", !IO),
        docall_struct_mono(SMono, 3.5, Res3),
        io.write_float(Res3, !IO),
        io.nl(!IO)
    ),

    begin("docall_struct_both", !IO),
    some [S, Res1, Res2] (
        S = struct_both(P2, P2),
        docall_struct_both(S, 3.5, Res1, 3.5, Res2),
        io.write_float(Res1, !IO),
        io.nl(!IO),
        io.write_float(Res2, !IO),
        io.nl(!IO)
    ),

    some [T, Res1, Res2] (
        T = foo(1.5, 2.5) - "foo",

        begin("docall_pair_poly", !IO),
        docall_pair_poly(T, 3.5, Res1),
        io.write_float(Res1, !IO),
        io.nl(!IO),

        begin("docall_pair_float", !IO),
        docall_pair_float(T, 3.5, Res2),
        io.write_float(Res2, !IO),
        io.nl(!IO)
    ),

    some [T, Res1, Res2, Res3] (
        T = {foo(1.5, 2.5)},

        begin("docall_tuple_poly", !IO),
        docall_tuple_poly(T, 3.5, Res1),
        io.write_float(Res1, !IO),
        io.nl(!IO),

        begin("docall_tuple_float", !IO),
        docall_tuple_float(T, 3.5, Res2),
        io.write_float(Res2, !IO),
        io.nl(!IO),

        begin("docall_tuple2_float", !IO),
        docall_tuple2_float({T}, 3.5, Res3),
        io.write_float(Res3, !IO),
        io.nl(!IO)
    ),

    begin("get_struct_pred_switch", !IO),
    some [S, Pred, Res] (
        S = struct_poly(P2),
        get_struct_pred_switch(S, Pred),
        Pred(3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    begin("get_struct_pred_disj", !IO),
    some [S, Res] (
        S = struct_poly(P2),
        unsorted_solutions(
            (pred(R::out) is multi :-
                get_struct_pred_disj(S, P),
                P(3.5, R)
            ), Res),
        io.write(Res, !IO),
        io.nl(!IO)
    ),

    begin("get_struct_pred_ite", !IO),
    some [S, Pred, Res] (
        S = struct_poly(P2),
        get_struct_pred_ite(S, Pred),
        Pred(3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    some [Res] (
        begin("meth1", !IO),
        meth1(1.5, 2.5, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    some [Res] (
        begin("meth2", !IO),
        meth2(1.5, 2.5, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    some [Res] (
        begin("meth3", !IO),
        meth3(P2, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    some [Res] (
        begin("meth4", !IO),
        meth4(P1, 2.5, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    some [Res] (
        begin("meth5", !IO),
        meth5(DoCall),
        DoCall(P2, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    begin("cast_inst", !IO),
    some [S1, S2, Res] (
        S1 = struct_poly(P2),
        cast_inst(S1, S2),
        docall_struct_poly_float_args(S2, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    begin("lost_inst", !IO),
    some [S1, S2, S3, Res] (
        S1 = struct_poly(P2),
        lost_inst(S1, S2),
        cast_inst(S2, S3),
        docall_struct_mono(S3, 3.5, Res),
        io.write_float(Res, !IO),
        io.nl(!IO)
    ),

    some [T, Res] (
        begin("map_recursive_type", !IO),
        T = foo(1.5, 2.5),
        map_recursive_type(cons(T, cons(T, cons(T, nil))), 3.5, Res),
        io.write(Res, !IO),
        io.nl(!IO)
    ).

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

:- pred begin(string::in, io::di, io::uo) is det.

begin(Message, !IO) :-
    io.nl(!IO),
    io.write_string(Message, !IO),
    io.nl(!IO),
    clear_float_regs(!IO).

:- pred clear_float_regs(io::di, io::uo) is det.

:- pragma foreign_proc("C",
    clear_float_regs(_IO0::di, _IO::uo),
    [will_not_call_mercury, promise_pure, thread_safe],
"
#if defined(MR_f)
    MR_f(1) = 0.0;
    MR_f(2) = 0.0;
    MR_f(3) = 0.0;
    MR_f(4) = 0.0;
    MR_f(5) = 0.0;
    MR_f(6) = 0.0;
    MR_f(7) = 0.0;
    MR_f(8) = 0.0;
    MR_f(9) = 0.0;
    MR_f(10) = 0.0;
#endif
").

clear_float_regs(!IO).

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

:- pred foo(float::in, float::in, float::in, float::out) is det.

foo(A, B, C, X) :-
    X = A + B*B + C*C*C.

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

:- pred docall(pred(T, T)::in(pred(in, out) is det), T::in, T::out) is det.
:- pragma no_inline(docall/3).

docall(P, X, Y) :-
    P(X, Y).

:- pred docall_inline(pred(T, T)::in(pred(in, out) is det), T::in, T::out)
    is det.
:- pragma inline(docall_inline/3).

docall_inline(P, X, Y) :-
    P(X, Y).

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

:- pred get_docall(pred(pred(T1, T1), T1, T1)).
:- mode get_docall(out(pred(pred(in, out) is det, in, out) is det)) is det.

:- pragma no_inline(get_docall/1).

get_docall(docall).

:- pred get_docall2(pred(pred(pred(T1, T1), T1, T1))).
:- mode get_docall2(out(pred(out(pred((pred(in, out) is det), in, out) is det))
    is det)) is det.
:- pragma no_inline(get_docall2/1).

get_docall2(get_docall).

:- pred get_docall3(pred(pred(pred(pred(T1, T1), T1, T1)))).
:- mode get_docall3(out(pred(out(pred(out(pred((pred(in, out) is det), in, out)
    is det)) is det)) is det)) is det.
:- pragma no_inline(get_docall3/1).

get_docall3(get_docall2).

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

:- pred docall_foreign_poly(pred(T, T)::in(pred(in, out) is det),
    T::in, T::out) is det.

docall_foreign_poly(P, X, Y) :- % for non-C backends
    P(X, Y).

:- pragma foreign_proc("C",
    docall_foreign_poly(P::in(pred(in, out) is det), X::in, Y::out),
    [may_call_mercury, promise_pure, thread_safe],
"
    EXPORT_docall_foreign_2(TypeInfo_for_T, P, X, &Y);
").

:- pred docall_foreign_2(pred(T, T)::in(pred(in, out) is det), T::in, T::out)
    is det.
:- pragma foreign_export("C",
    docall_foreign_2(in(pred(in, out) is det), in, out),
    "EXPORT_docall_foreign_2").

docall_foreign_2(P, X, Y) :-
    P(X, Y).

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

:- pred docall_foreign_float(pred(float, float)::in(pred(in, out) is det),
    float::in, float::out) is det.

:- pragma foreign_proc("C",
    docall_foreign_float(P::in(pred(in, out) is det), X::in, Y::out),
    [may_call_mercury, promise_pure, thread_safe],
"
    EXPORT_docall_foreignf_2(P, X, &Y);
").

docall_foreign_float(P, X, Y) :- % for non-C backends
    P(X, Y).

:- pred docall_foreign_float_2(pred(float, float)::in(pred(in, out) is det),
    float::in, float::out) is det.

:- pragma foreign_export("C",
    docall_foreign_float_2(in(pred(in, out) is det), in, out),
    "EXPORT_docall_foreignf_2").

docall_foreign_float_2(P, X, Y) :-
    P(X, Y).

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

:- type struct(T)
    --->    struct_poly(pred(T, T))
    ;       struct_mono(pred(float, float)).

:- inst struct
    --->    struct_poly(pred(in, out) is det)
    ;       struct_mono(pred(in, out) is det).

:- pred docall_struct_poly_generic_args(struct(T)::in(struct), T::in, T::out)
    is det.
:- pragma no_inline(docall_struct_poly_generic_args/3).

docall_struct_poly_generic_args(S, X, Y) :-
    (
        S = struct_poly(P),
        P(X, Y)
    ;
        S = struct_mono(_),
        unexpected($module, $pred)
    ).

:- pred docall_struct_poly_float_args(struct(float)::in(struct),
    float::in, float::out) is det.
:- pragma no_inline(docall_struct_poly_float_args/3).

docall_struct_poly_float_args(S, X, Y) :-
    (
        S = struct_poly(P),
        P(X, Y)
    ;
        S = struct_mono(_),
        unexpected($module, $pred)
    ).

:- pred docall_struct_mono(struct(T)::in(struct), float::in, float::out)
    is det.
:- pragma no_inline(docall_struct_mono/3).

docall_struct_mono(S, X, Y) :-
    (
        S = struct_mono(P),
        P(X, Y)
    ;
        S = struct_poly(_),
        unexpected($module, $pred)
    ).

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

:- type struct_both(T)
    --->    struct_both(pred(T, T), pred(float, float)).

:- inst struct_both
    --->    struct_both(pred(in, out) is det, pred(in, out) is det).

:- pred docall_struct_both(struct_both(T)::in(struct_both), T::in, T::out,
    float::in, float::out) is det.
:- pragma no_inline(docall_struct_both/5).

docall_struct_both(struct_both(P, Q), !X, !Y) :-
    P(!X),
    Q(!Y).

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

:- inst pairpred
    --->    (pred(in, out) is det) - ground.

:- pred docall_pair_poly(pair(pred(T, T), U)::in(pairpred), T::in, T::out)
    is det.
:- pragma no_inline(docall_pair_poly/3).

docall_pair_poly(P - _, X, Y) :-
    P(X, Y).

:- pred docall_pair_float(pair(pred(float, float), string)::in(pairpred),
    float::in, float::out) is det.
:- pragma no_inline(docall_pair_float/3).

docall_pair_float(P - _, X, Y) :-
    P(X, Y).

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

:- inst tuplepred
    --->    { pred(in, out) is det }.

:- inst tupletuplepred
    --->    { tuplepred }.

:- pred docall_tuple_poly({ pred(T, T) }::in(tuplepred), T::in, T::out) is det.
:- pragma no_inline(docall_tuple_poly/3).

docall_tuple_poly({P}, X, Y) :-
    P(X, Y).

:- pred docall_tuple_float({ pred(float, float) }::in(tuplepred),
    float::in, float::out) is det.
:- pragma no_inline(docall_tuple_float/3).

docall_tuple_float({P}, X, Y) :-
    P(X, Y).

:- pred docall_tuple2_float({{ pred(float, float) }}::in(tupletuplepred),
    float::in, float::out) is det.
:- pragma no_inline(docall_tuple2_float/3).

docall_tuple2_float({{P}}, X, Y) :-
    P(X, Y).

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

    % Test inst merging in switches.
    %
:- pred get_struct_pred_switch(struct(float)::in(struct),
    pred(float, float)::out(pred(in, out) is det)) is det.
:- pragma no_inline(get_struct_pred_switch/2).

get_struct_pred_switch(struct_poly(P), P).
get_struct_pred_switch(struct_mono(P), P).

    % Test inst merging in disjunctions.
    %
:- pred get_struct_pred_disj(struct(float)::in(struct),
    pred(float, float)::out(pred(in, out) is det)) is multi.
:- pragma no_inline(get_struct_pred_disj/2).

get_struct_pred_disj(struct_poly(P), P).
get_struct_pred_disj(struct_mono(P), P).
get_struct_pred_disj(_, foo(1.5, 2.5)).
get_struct_pred_disj(_, foo(1.5, 2.5)).

    % Test insts merging in if-then-elses.
    %
:- pred get_struct_pred_ite(struct(float)::in(struct),
    pred(float, float)::out(pred(in, out) is det)) is det.
:- pragma no_inline(get_struct_pred_ite/2).

get_struct_pred_ite(S, P) :-
    ( if S = struct_poly(P0) then
        P = P0
    else if S = struct_mono(P0) then
        P = P0
    else
        P = foo(-1.5, -2.5) % dummy
    ).

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

:- typeclass tc(T) where [
    pred meth1(T::in, T::in, T::in, T::out) is det,
    pred meth2(T::in, T::in, T::in, T::out) is det,
    pred meth3(pred(T, T)::in(pred(in, out) is det), T::in, T::out) is det,
    pred meth4(pred(float, T, T)::in(pred(in, in, out) is det), float::in,
        T::in, T::out) is det,
    pred meth5(pred(pred(T, T), T, T)::out(pred(pred(in, out) is det, in, out)
        is det)) is det
].

:- instance tc(float) where [
    pred(meth1/4) is foo,
    ( meth2(A, B, C, D) :-
        foo(A, B, C, D)
    ),
    ( meth3(P, C, D) :-
        P(C, D)
    ),
    ( meth4(P, B, C, D) :-
        P(B, C, D)
    ),
    pred(meth5/1) is get_docall
].

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

    % Check we are able to introduce wrappers after losing the inst
    % then "recovering" it.
    %
:- pred lost_inst(struct(float)::in, struct(float)::out) is det.
:- pragma no_inline(lost_inst/2).

lost_inst(S0, S) :-
    cast_inst(S0, S1),
    (
        S1 = struct_poly(P),
        S = struct_mono(P)  % wrapper here
    ;
        S1 = struct_mono(P),
        S = struct_mono(P)
    ).

:- pred cast_inst(struct(float)::in, struct(float)::out(struct)) is det.

:- pragma foreign_proc("C",
    cast_inst(S0::in, S::out(struct)),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    S = S0;
").
:- pragma foreign_proc("C#",
    cast_inst(S0::in, S::out(struct)),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    S = S0;
").
:- pragma foreign_proc("Java",
    cast_inst(S0::in, S::out(struct)),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    S = S0;
").

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

:- type recursive_type(T)
    --->    nil
    ;       cons(pred(T, T), recursive_type(T)).

:- inst recursive_inst
    --->    nil
    ;       cons(pred(in, out) is det, recursive_inst).

:- pred map_recursive_type(recursive_type(T)::in(recursive_inst),
    T::in, list(T)::out) is det.

map_recursive_type(nil, _, []).
map_recursive_type(cons(P, Ps), X, [Y | Ys]) :-
    P(X, Y),
    map_recursive_type(Ps, X, Ys).

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

:- type existstruct(T)
    --->    some [U] existstruct(pred(T, T), U).

:- inst existstruct
    --->    existstruct(pred(in, out) is det, ground).

% XXX mode checking fails
%
% :- pred docall_existstruct(existstruct(T)::in(existstruct), T::in, T::out)
%     is det.
% :- pragma no_inline(docall_existstruct/3).
%
% docall_existstruct(existstruct(P, _), X, Y) :-
%     P(X, Y).
