--  This package defines an abstract data type in which both magnitude and
--  dimensionality are maintained across numeric operations and assignments.

--  The approach is based on a design first illustrated in the original Red
--  language proposal and elaborated upon by both Paul Hilfinger [1] and Jean
--  Ichbiah [2].

--  The idea is to use discriminants to specify the dimensionality and to rely
--  on the language rules to ensure that they remain consistent. In particular,
--  we define three discriminants: one each for mass, distance, and time
--  dimensionality. A "length" unit would have a distance discriminant value of
--  1, for example, with the time and mass discriminants set to zero. An "area"
--  unit, on the other hand, would have a distance discriminant value of 2. A
--  dimensionless scalar value would have all three discriminants set to zero.

--  We rely on the fact that discriminants cannot be changed by assignments
--  (unless they have a default and take that default, which we do not do).
--  Constraint_Error is raised if the discriminants of the left-hand side differ
--  from those on the right-hand side of the assignment statement.

--  For the sake of readability, the package also defines named subtypes
--  corresponding to the discriminant settings. Specifically, subtypes "Mass",
--  "Distance", "Time", and "Scalar" are defined. Child packages can define
--  constants and subtypes for additional units, such as ergs or speed, in terms
--  of metric or imperial systems (for example).

--  [1] P. Hilfinger, Abstraction Mechanisms and Language Design: MIT Press,
--  1983.

--  [2] J. Ichbiah, et al., Rationale for the Design of the Ada Programming
--  Language, 1986.

generic
   type Real is digits <>;
package Dimensioned_Units is

   type Unit (M, D, T : Integer) is private;
   --  The abstract data type for a dimensionally consistent unit. The three
   --  discriminants M, D, and T represent the dimensions for mass, distance,
   --  and time, respectively.

   Dimension_Error : exception;
   --  Raised only by "-" and "+" having both formal parameters of type Unit.
   --  Note that assignment of units having different discriminant values will
   --  raise Constraint_Error;

   function "*" (Left, Right : Unit) return Unit;
   --  Returns a value with the corresponding dimensions added and the
   --  magnitudes multiplied.

   function "*" (Left : Unit;  Right : Real) return Unit;
   --  Returns a value with the dimensions of Left and the magnitude of Left
   --  multiplied by Right.

   function "*" (Left : Real;  Right : Unit) return Unit;
   --  Returns a value with the dimensions of Right and the magnitude of Right
   --  multiplied by Left.

   function "+" (Left, Right : Unit) return Unit;
   --  Raises Dimension_Error if the dimension discriminants of Left do not
   --  equal the corresponding discriminants of Right.
   --  Returns a value with the corresponding dimensions unchanged and the
   --  magnitudes added.

   function "-" (Left, Right : Unit) return Unit;
   --  Raises Dimension_Error if the dimension discriminants of Left do not
   --  equal the corresponding discriminants of Right.
   --  Returns a value with the corresponding dimensions unchanged and the
   --  magnitudes subtracted.

   function "-" (Left : Unit) return Unit;
   --  Returns a value with the corresponding dimensions unchanged and the
   --  sign of the magnitude switched.

   function "/" (Left, Right : Unit) return Unit;
   --  Returns a value with the corresponding dimensions subtracted and the
   --  magnitudes divided.

   function "/" (Left : Unit;  Right : Real) return Unit;
   --  Returns a value with the dimensions of Left and the magnitude of Left
   --  divided by Right.

   function "/" (Left : Real;  Right : Unit) return Unit;
   --  Returns a value with the dimensions of Right negated and a magnitude
   --  equal to Left divided by the magnitude of Right.

   function "**" (Left : Unit;  Right : Integer) return Unit;
   --  Returns a value with the dimensions of Left multiplied by Right and a
   --  magnitude equal to that of Left raised to the power of Right.

   function Value (This : Unit) return Real;
   --  Returns the magnitude of This.

   subtype Mass     is Unit (M => 1, D => 0, T => 0);
   subtype Distance is Unit (M => 0, D => 1, T => 0);
   subtype Time     is Unit (M => 0, D => 0, T => 1);
   subtype Scalar   is Unit (M => 0, D => 0, T => 0);

private

   type Unit (M, D, T : Integer) is
      record
         Magnitude : Real := 0.0;
      end record;

   function Make (Value : Real;  M,D,T : Integer) return Unit;
   --  Creates a unit value with the magnitude set to Value and the dimension
   --  discriminants set to the corresponding remaining parameters.

end Dimensioned_Units;

