%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et wm=0 tw=0
%---------------------------------------------------------------------------%
% Copyright (C) 2016 The Mercury team.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: unsigned_int.m.
% Main authors: wangp.
% Stability: medium.
%
% Predicates and functions for dealing with machine-size integer numbers.
%
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- module unsigned_int.
:- interface.

:- import_module int.

:- type unsigned_int.

% :- instance enum(unsigned_int).

    % Convert a signed integer to an unsigned integer.
    % Fails if the signed integer is negative.
    %
:- pred from_int(int, unsigned_int).
:- mode from_int(in, out) is semidet.

    % Convert a signed integer to an unsigned integer.
    % Throws an exception if the signed integer is negative.
    %
:- func det_from_int(int) = unsigned_int.

    % Convert an unsigned integer to a signed integer.
    % Fails if the unsigned integer is greater than int.max_int.
    %
:- pred to_int(unsigned_int, int).
:- mode to_int(in, out) is semidet.

    % Convert an unsigned integer to a signed integer.
    % Throws an exception if the unsigned integer is greater than int.max_int.
    %
:- func det_to_int(unsigned_int) = int.

    % Convert (cast) signed integer to an unsigned integer.
    % If the `int' value can be represented in an `unsigned_int' then it is
    % unchanged. Otherwise, the value is converted by adding
    % `max_unsigned_int + 1' so that the value is in the range of
    % `unsigned_int'.
    %
:- func cast_from_int(int) = unsigned_int.

    % Convert (cast) unsigned integer to an signed integer.
    % If the `unsigned_int' value can be represented in an `int' then it is
    % unchanged. Otherwise, the result is implementation defined, i.e. it
    % depends on the behaviour of the underlying compiler.
    %
:- func cast_to_int(unsigned_int) = int.

    % Comparisons.
    %
:- pred (unsigned_int::in) < (unsigned_int::in) is semidet.
:- pred (unsigned_int::in) > (unsigned_int::in) is semidet.
:- pred (unsigned_int::in) =< (unsigned_int::in) is semidet.
:- pred (unsigned_int::in) >= (unsigned_int::in) is semidet.

    % Maximum.
    %
:- func max(unsigned_int, unsigned_int) = unsigned_int.

    % Minimum.
    %
:- func min(unsigned_int, unsigned_int) = unsigned_int.

    % Integer arithmetic (modulo bits_per_unsigned_int).
    %
:- func unsigned_int + unsigned_int = unsigned_int.
:- func unsigned_int - unsigned_int = unsigned_int.
:- func unsigned_int * unsigned_int = unsigned_int.

% div
% //
% /
% unchecked_quotient
% mod
% rem

    % even(X) is equivalent to (X mod 2 = 0).
    %
:- pred even(unsigned_int::in) is semidet.

    % odd(X) is equivalent to (not even(X)), i.e. (X mod 2 = 1).
    %
:- pred odd(unsigned_int::in) is semidet.

    % Left shift.
    % (If shift counts are unsigned then we do not need to define behaviour for
    % negative shifts, but it may be inconvenient for the user.)
    %
:- func unsigned_int << unsigned_int = unsigned_int.

    % unchecked_left_shift(X, Y) is the same as X << Y
    % except that the behaviour is undefined if Y is greater or equal to the
    % result of `bits_per_unsigned_int/1'.
    % It will typically be implemented more efficiently than X << Y.
    %
:- func unchecked_left_shift(unsigned_int, unsigned_int) = unsigned_int.

    % Right shift.
    %
:- func unsigned_int >> unsigned_int = unsigned_int.

    % unchecked_right_shift(X, Y) is the same as X >> Y
    % except that the behaviour is undefined if Y is greater than or equal to
    % the result of `bits_per_unsigned_int/1'.
    % It will typically be implemented more efficiently than X >> Y.
    %
:- func unchecked_right_shift(unsigned_int, unsigned_int) = unsigned_int.

    % Bitwise and.
    %
:- func unsigned_int /\ unsigned_int = unsigned_int.

    % Bitwise or.
    %
:- func unsigned_int \/ unsigned_int = unsigned_int.

    % Bitwise exclusive or (xor).
    %
:- func xor(unsigned_int, unsigned_int) = unsigned_int.

    % Bitwise complement.
    %
:- func \ unsigned_int = unsigned_int.

    % Unary plus.
    %
:- func + unsigned_int = unsigned_int.

    % Unary minus.
    %
:- func - unsigned_int = unsigned_int.

    % Constants.
    %
:- func zero = unsigned_int.
:- func one = unsigned_int.

    % max_unsigned_int is the maximum value of an unsigned_int on this machine.
    %
:- func max_unsigned_int = unsigned_int.

    % bits_per_unsigned_int is the number of bits in an unsigned_int on this
    % machine, equal to int.bits_per_int.
    %
:- func bits_per_unsigned_int = int.

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

:- implementation.

:- import_module exception.
:- import_module math.

:- pragma foreign_type("C", unsigned_int, "MR_Unsigned",
    [can_pass_as_mercury_type])
    where   equality is unsigned_int_equal,
            comparison is unsigned_int_compare.

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

from_int(I, U) :-
    I >= 0,
    U = cast_from_int(I).


det_from_int(I) = U :-
    ( if I >= 0 then
        U = cast_from_int(I)
    else
        throw(math.domain_error("unsigned_int.from_int: negative value"))
    ).

to_int(U, I) :-
    U =< cast_from_int(max_int),
    I = cast_to_int(U).

det_to_int(U) = I :-
    ( if U =< cast_from_int(max_int) then
        I = cast_to_int(U)
    else
        throw(math.domain_error("unsigned_int.to_int: integer too large"))
    ).

:- pragma foreign_proc("C",
    cast_from_int(I::in) = (U::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    U = (MR_Unsigned) I;
").

:- pragma foreign_proc("C",
    cast_to_int(U::in) = (I::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    I = (MR_Integer) U;
").

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

:- pred unsigned_int_equal(unsigned_int::in, unsigned_int::in) is semidet.

:- pragma foreign_proc("C",
    unsigned_int_equal(X::in, Y::in),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    SUCCESS_INDICATOR = (X == Y);
").

:- pred unsigned_int_compare(comparison_result::uo,
    unsigned_int::in, unsigned_int::in) is det.

:- pragma foreign_proc("C",
    unsigned_int_compare(Rel::uo, X::in, Y::in),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    if (X == Y) {
        Rel = MR_COMPARE_EQUAL;
    } else if (X < Y) {
        Rel = MR_COMPARE_LESS;
    } else {
        Rel = MR_COMPARE_GREATER;
    }
").

:- pragma foreign_proc("C",
    (X::in) < (Y::in),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    SUCCESS_INDICATOR = (X < Y);
").

:- pragma foreign_proc("C",
    (X::in) > (Y::in),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    SUCCESS_INDICATOR = (X > Y);
").

:- pragma foreign_proc("C",
    (X::in) =< (Y::in),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    SUCCESS_INDICATOR = (X <= Y);
").

:- pragma foreign_proc("C",
    (X::in) >= (Y::in),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    SUCCESS_INDICATOR = (X >= Y);
").

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

max(X, Y) = ( if X > Y then X else Y ).

min(X, Y) = ( if X < Y then X else Y ).

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

:- pragma foreign_proc("C",
    (X::in) + (Y::in) = (Z::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Z = X + Y;
").

:- pragma foreign_proc("C",
    (X::in) - (Y::in) = (Z::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Z = X - Y;
").

:- pragma foreign_proc("C",
    (X::in) * (Y::in) = (Z::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Z = X * Y;
").

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

:- pragma foreign_proc("C",
    (X::in) /\ (Y::in) = (Z::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Z = X & Y;
").

:- pragma foreign_proc("C",
    (X::in) \/ (Y::in) = (Z::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Z = X | Y;
").

:- pragma foreign_proc("C",
    xor(X::in, Y::in) = (Z::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Z = X ^ Y;
").

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

even(X) :-
    (X /\ one) = zero.

odd(X) :-
    (X /\ one) \= zero.

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

X << Y = Z :-
    % if we allow signed Y then we would need to consider negative Y
    ( if Y >= cast_from_int(bits_per_unsigned_int) then
        Z = zero
    else
        Z = unchecked_left_shift(X, Y)
    ).

:- pragma foreign_proc("C",
    unchecked_left_shift(X::in, Y::in) = (Z::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Z = X << Y;
").

X >> Y = Z :-
    ( if Y >= cast_from_int(bits_per_unsigned_int) then
        Z = zero
    else
        Z = unchecked_right_shift(X, Y)
    ).

:- pragma foreign_proc("C",
    unchecked_right_shift(X::in, Y::in) = (Z::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Z = X >> Y;
").

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

:- pragma foreign_proc("C",
    \ (X::in) = (Z::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Z = ~X;
").

:- pragma foreign_proc("C",
    + (X::in) = (Z::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Z = X;
").

:- pragma foreign_proc("C",
    - (X::in) = (Z::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Z = -X;
").

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

:- pragma foreign_proc("C",
    zero = (Zero::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Zero = 0;
").

:- pragma foreign_proc("C",
    one = (One::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    One = 1;
").

:- pragma foreign_proc("C",
    max_unsigned_int = (Max::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Max = ~ (MR_Unsigned) 0;
").

bits_per_unsigned_int = int.bits_per_int.

%---------------------------------------------------------------------------%
:- end_module unsigned_int.
%---------------------------------------------------------------------------%
