% -----------------------------------------------------------------------------
%  (C) Altran Praxis Limited
% -----------------------------------------------------------------------------
% 
%  The SPARK toolset is free software; you can redistribute it and/or modify it
%  under terms of the GNU General Public License as published by the Free
%  Software Foundation; either version 3, or (at your option) any later
%  version. The SPARK toolset is distributed in the hope that it will be
%  useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
%  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
%  Public License for more details. You should have received a copy of the GNU
%  General Public License distributed with the SPARK toolset; see file
%  COPYING3. If not, go to http://www.gnu.org/licenses for a complete copy of
%  the license.
% 
% =============================================================================


%###############################################################################
% PURPOSE
%-------------------------------------------------------------------------------
% This module defines the X iss Expr predicate, which takes a ground
% expression Expr and attempts to evaluate it, unifying X with the result
% if this is possible.  It is thus rather like the built-in is predicate of
% Prolog, but with three important differences.  Firstly, it will prevent
% evaluations which would lead to an execution error, such as X is 1/0.
% Secondly, it does not evaluate mod expressions at presence, since no
% definition of mod is built into the Simplifier.  (This is for historical
% reasons, because Pascals mod differs from that of Ada, yet both languages
% are in principle supported by the Simplifier.)  Finally, it returns a
% structure like those accepted by signed_integer, where the result is
% negative, i.e. returning a negated, rather than a negative, literal.
%###############################################################################


%===============================================================================
% exp_iss(-Value, +Expression).
%-------------------------------------------------------------------------------
% See below for iss/2
%
% Special case for ** operator
%===============================================================================
X exp_iss A**B :-
    P iss A,
    Q iss B,
    !,
    (
       Q = 0,
       X = 1
    ;
       integer(Q),
       Q > 0,
       Q1 is Q - 1,
       !,
       P1 exp_iss P**Q1,
       !,
       X iss P * P1
    ),
    !.

%===============================================================================
% iss(-Value, +Expression).
%-------------------------------------------------------------------------------
% The Value iss Expression predicate, takes a ground expression as
% Expression and attempts to evaluate it, unifying Value with the result
% where possible.  It is thus rather like the built-in 'is' predicate of
% Prolog, but with the following important differences:
% +It will prevent evaluations which would lead to an execution error, such
% as X is 1/0.
% +It returns negated positive literals (-(42)), rather that negative
% literals (-42).
%===============================================================================

% Variable.
_X iss V :-
    var(V), !, fail.

% Positive integer.
A iss A :-
    integer(A), A>=0, !.

% Negative integer.
X iss A :-
    integer(A),
    A<0,
    !,
    A1 is -A,
    X=(-A1),
    !.

% Double negation.
X iss -(-A) :-
    X iss A, !.

% Unary minus.
X iss -(A) :-
    P iss A,
    !,
    (
       P=0, X=0
    ;
       integer(P), P>0,
       X=(-P)
    ;
       P=(-X), X>0
    ), !.

% Addition.
X iss A+B :-
    P iss A,
    Q iss B,
    !,
    X1 is P+Q,
    (
       X1>=0,
       X=X1
    ;
       X1<0,
       X2 is -X1,
       X=(-X2)
    ), !.

% Subtraction.
X iss A-B :-
    X iss A+(-B), !.

% Multiplication.
X iss A*B :-
    P iss A,
    Q iss B,
    !,
    X1 is P*Q,
    (
       X1>=0,
       X=X1
    ;
       X1<0,
       X2 is -X1,
       X=(-X2)
    ), !.

% Division.
X iss A div B :-
    P iss A,
    Q iss B,
    !,
    Q\=0,
    eval_div(P, Q, X1),
    (
       X1>=0,
       X=X1
    ;
       X1<0,
       X2 is -X1,
       X=(-X2)
    ), !.

% Modulus (SPARK definition).
X iss A mod B :-
    get_provenance_framework(spark),
    !,
    P iss A,
    Q iss B,
    !,
    Q \= 0,
    eval_div(P, Q, X1),
    Remainder iss P - (X1 * Q),
    (
       Remainder = 0,
       X = 0
    ;
       signed_integer(P),
       signed_integer(Q),
       (
          (
             P >= 0,
             Q > 0
          ;
             P =< 0,
             Q < 0
          ),
          X = Remainder
       ;
          (
             P >= 0,
             Q < 0
          ;
             P =< 0,
             Q > 0
          ),
          X iss Remainder + Q
       )
    ;
       X = (P mod Q)
    ),
    !.

% Modulus (Pascal definition).
X iss A mod B :-
    get_provenance_framework(pascal),
    !,
    P iss A,
    Q iss B,
    !,
    Q \= 0,
    eval_div(P, Q, X1),
    Remainder iss P - (X1 * Q),
    (
       Remainder = 0,
       X = 0
    ;
       Q = 1,
       X = 0
    ;
       X = (P mod Q)
    ),
    !.

% Exponentiation (raise to the power of).
% guard abs of both arguments to prevent
% attempt to evaluate gigantic values that would
% take too long or just run out of memory
X iss A**B :-
    P iss abs(A),
    Q iss abs(B),
    simplify(P <= 1024, true),
    simplify(Q <= 1024, true),
    X exp_iss A**B.
	
% Absolute value.
X iss abs(A) :-
    P iss A,
    !,
    (
       integer(P),
       P >= 0,
       X = P
    ;
       signed_integer(P),
       P < 0,
       X iss -P
    ),
    !.
%===============================================================================


%===============================================================================
% rational_expression(+Expression).
%-------------------------------------------------------------------------------
% Succeeds if Expression is a nice rationals-only expression.
%===============================================================================

rational_expression(A)      :- var(A), !, fail.
rational_expression(A)      :- integer(A), !.
rational_expression(-A)     :- !, rational_expression(A).
rational_expression(abs(A)) :- !, rational_expression(A).
rational_expression(A+B)    :- !, rational_expression(A), !, rational_expression(B).
rational_expression(A-B)    :- !, rational_expression(A), !, rational_expression(B).
rational_expression(A*B)    :- !, rational_expression(A), !, rational_expression(B).
rational_expression(A/B)    :- !, rational_expression(A), !, rational_expression(B).
rational_expression(A**B)   :- !, rational_expression(A), !, intexp(B).
%===============================================================================


%===============================================================================
% evaluate_rational_expression(+RationalExpression, -Value).
%-------------------------------------------------------------------------------
% Given a RationalExpression, evaluate it and return its Value, as a
% rational literal.
%
% Note that the legal values returned are defined by base_rational(V),
% which succeeds if V has been reduced to its 'simplest' base form. So, for
% instance, base_rational(3) and base_rational(4/3) succeed whereas
% base_rational(8/2) and base_rational(-(- 1)) both fail.
%===============================================================================

% Absolute value.
evaluate_rational_expression(abs(R), Value) :-
    evaluate_rational_expression(R, Rvalue),
    (
       positive_rational(Rvalue),
       Value = Rvalue
    ;
       negative_rational(Rvalue),
       Rvalue = (-Value)
    ),
    !.

% Addition.
evaluate_rational_expression(Rx + Ry, Value) :-
    evaluate_rational_expression(Rx, Xvalue),
    evaluate_rational_expression(Ry, Yvalue),
    split_rational(Xvalue, Xn, Xd),
    split_rational(Yvalue, Yn, Yd),
    Top iss Xn * Yd + Yn * Xd,
    Bottom iss Xd * Yd,
    !,
    make_base_rational(Top, Bottom, Value).

% Subtraction.
evaluate_rational_expression(Rx - Ry, Value) :-
    evaluate_rational_expression(Rx, Xvalue),
    evaluate_rational_expression(Ry, Yvalue),
    split_rational(Xvalue, Xn, Xd),
    split_rational(Yvalue, Yn, Yd),
    Top iss Xn * Yd - Yn * Xd,
    Bottom iss Xd * Yd,
    !,
    make_base_rational(Top, Bottom, Value).

% Multiplication.
evaluate_rational_expression(Rx * Ry, Value) :-
    evaluate_rational_expression(Rx, Xvalue),
    evaluate_rational_expression(Ry, Yvalue),
    split_rational(Xvalue, Xn, Xd),
    split_rational(Yvalue, Yn, Yd),
    Top iss Xn * Yn,
    Bottom iss Xd * Yd,
    !,
    make_base_rational(Top, Bottom, Value).

% Exponentiation (raise to the power of).
evaluate_rational_expression(R ** I, Value) :-
    evaluate_rational_expression(R, Rvalue),
    intexp(I),
    Ivalue iss I,
    (
       Ivalue = 0,
       Value = 1
    ;
       Ivalue = 1,
       Value = Rvalue
    ;
       Ivalue < 0,
       R \= 0,
       split_rational_alt_sign(Rvalue, Rn, Rd),
       make_base_rational(Rd, Rn, OneOverR),
       Ineg is -Ivalue,
       !,
       evaluate_rational_expression(OneOverR ** Ineg, Value)
    ;
       Idiv2 iss I div 2,
       Imod2 iss Ivalue - 2 * Idiv2,
       evaluate_rational_expression(Rvalue * Rvalue, Rsquared),
       evaluate_rational_expression(Rsquared ** Idiv2, SqValue),
       (
          Imod2 = 0,
          Value = SqValue
       ;
          evaluate_rational_expression(Rvalue * SqValue, Value)
       )
    ),
    !.

% Base case.
evaluate_rational_expression(R, R) :-
    base_rational(R),
    !.

% Division.
evaluate_rational_expression(Rx / Ry, Value) :-
    evaluate_rational_expression(Rx, Xvalue),
    evaluate_rational_expression(Ry, Yvalue),
    split_rational(Xvalue, Xn, Xd),
    split_rational_alt_sign(Yvalue, Yn, Yd),
    Top iss Xn * Yd,
    Bottom iss Xd * Yn,
    !,
    make_base_rational(Top, Bottom, Value).

% Unary minus.
evaluate_rational_expression(-R, Value) :-
        evaluate_rational_expression(R, Rvalue),
        (
           positive_rational(Rvalue),
           (
              Rvalue = 0,
              Value  = 0
           ;
              Value = (-Rvalue)
           )
        ;
           negative_rational(Rvalue),
           Rvalue = (-Value)
        ),
        !.
%===============================================================================


%===============================================================================
% base_rational(+Expression).
%-------------------------------------------------------------------------------
% Is successful where Expression is of the form: [-](I[/J]),
% where I,J are integers, I>=0, J>0 and the greatest common divisor of I
% and J is 1.
%
% For example, accepted cases:
% -(1), 0, 42, 42/11, -(7/3).
%
% Rejected cases:
% -(0)     [should be 0 instead]
% 7/1      [should be 7 instead]
% (-7)/2   [should be -(7/2) instead]
% /4       [should be 5/2 instead]
% 3/(-(1)) [should be -(3/1) instead]
% 4/(3/2)  [should be 8/3 instead].
%===============================================================================

base_rational(-R) :-
    positive_rational(R),
        R \= 0.

base_rational(R) :-
    positive_rational(R).

positive_rational(I) :-
    integer(I), I>=0.

positive_rational(I/J) :-
    integer(I), I>=0,
    integer(J), J>1,
    gcd(I, J, 1).

negative_rational(-R) :-
    positive_rational(R),
    R \= 0.
%===============================================================================


%===============================================================================
% strict_rational(+Expression).
%-------------------------------------------------------------------------------
% Succeed where Expression is a rational literal that isn't a signed
% integer.
%===============================================================================

strict_rational(R) :-
    base_rational(R),
    \+ signed_integer(R).
%===============================================================================


%===============================================================================
% split_rational(+RationalLiteral, -NumeratorPart, -DenominatorPart).
%-------------------------------------------------------------------------------
% Split a RationalLiteral into a NumeratorPart and DenominatorPart.
%
% Note that the numerator carries the sign information, while the
% denominator is always strictly +ve.
%===============================================================================

split_rational(-(I/J), -I, J) :-
    integer(I), I>0,
    integer(J), J>0.

split_rational(I/J, I, J) :-
    integer(I), I>0,
    integer(J), J>0.

split_rational(I, I, 1) :-
    signed_integer(I).
%===============================================================================


%===============================================================================
% split_rational_alt_sign(+RationalLiteral, -NumeratorPart, -DenominatorPart).
%-------------------------------------------------------------------------------
% Split a RationalLiteral into a NumeratorPart and DenominatorPart.
%
% Note that, unlike split_rational/3, the denominator carries the sign
% information, while the numerator is always strictly +ve.
%
% The converse operation is needed for inverting. For example, to turn: -(3/4)
% upside down. split_rational_alt_sign(-(3/4), N, D) gives:
% N=3, D=(-(4))
% From which we can form:
% -(4/3),
% Which will yield numerator (-(4)) and denominator 3 via split_rational/3.
%===============================================================================

split_rational_alt_sign(-(I/J), I, -J) :-
    integer(I), I>0,
    integer(J), J>0.
split_rational_alt_sign(I/J, I, J) :-
    integer(I), I>0,
    integer(J), J>0.
split_rational_alt_sign(I, I, 1) :-
    integer(I), I>0.
split_rational_alt_sign(-I, I, (- 1)) :-
    integer(I), I>0.
%===============================================================================


%===============================================================================
% make_base_rational(+Top, +Bottom, -Value).
%-------------------------------------------------------------------------------
% Given a signed integer Top and a (strictly positive) integer Bottom, form
% the (unique) rational literal whose Value is that of the expression
% Top/Bottom.
%===============================================================================

make_base_rational(Top, Bottom, Value) :-
    signed_integer(Top),
    AbsTop is abs(Top),
    integer(Bottom),
    Bottom > 0,
    (
       Bottom = 1,
       Value = Top
    ;
       gcd(AbsTop, Bottom, GCD),
       GCD > 0,
       (
          GCD = 1,
          (
             Top = AbsTop,
             !,
             Value = Top / Bottom
          ;
             Value = - (AbsTop / Bottom)
          )
       ;
          ReducedAbsTop iss AbsTop div GCD,
          ReducedBottom iss Bottom div GCD,
          (
             ReducedBottom = 1,
             !,
             (
                Top = AbsTop,
                !,
                Value = ReducedAbsTop
             ;
                Value = (- ReducedAbsTop)
             )
          ;
             Top = AbsTop,
             !,
             Value = ReducedAbsTop / ReducedBottom
          ;
             Value = - (ReducedAbsTop / ReducedBottom)
          )
       )
    ),
    !.
%===============================================================================


%###############################################################################
% TESTS
%###############################################################################

:- evaluate_rational_expression((1/2) ** 0, 1).
:- evaluate_rational_expression((1/2) ** 1, (1/2)).
:- evaluate_rational_expression((1/2) ** 2, (1/4)).
:- evaluate_rational_expression((1/2) ** 3, (1/8)).
:- evaluate_rational_expression((1/2) ** 4, (1/16)).
:- evaluate_rational_expression((1/2) ** 5, (1/32)).
:- evaluate_rational_expression((1/2) ** -1, 2).
:- evaluate_rational_expression((1/2) ** -2, 4).

%###############################################################################
% END-OF-FILE
