Using Unchecked_Conversion to read in values and convert to a custom type

1.1k views Asked by At

I'm quite confused on how 'Size and 'Component_Size work when reading input from a file and trying to use Unchecked_Conversion. I know that to sucsessfully use Unchecked_Conversion both the Source and the Target need to be the same size. I'm reading in input from a file like 000100000101001 and want to use Unchecked Conversion to put that into an array of Bits. However, the conversion always seems to fail because of them not being the same size or being too small.

    with Ada.Unchecked_Conversion;
    with Ada.Text_IO; use Ada.Text_IO;
    with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

    procedure main is
        type Bit is mod 2 with size => 1;
        type Bit_Array is array(Positive range <>) of Bit with pack;
        subtype Bit15 is Bit_Array(1 .. 15); //subtypes because can't use conversion on unconstrainted type
        subtype Bit11 is Bit_Array(1 .. 11);

        function StringA_to_Bit15 is
           new Ada.Unchecked_Conversion(source => String, target => Bit15);


        begin
        while not end_of_file loop
            declare
                s: String := get_line; //holding first line of input
                len: constant Natural := (if s'length-3 = 15 
                                        then 15
                                        else 11); 
                i: Integer;
                ba: Bit_Array(1 .. len); //constrain a Bit_Array based on length of input
            begin
                ba := String_to_Bit15(s);
                new_line;
            end;
        end loop;

Here are my types, Bit which can only be 0 or 1 with size to 1 bit. Bit_Array is just an array of Bit's which is unconstrained because my input can either be 15-Bits long or 11-Bits long. My thought was to just read in the first line into a String and convert it into a Bit_Array. This doesn't work because String and every other primitive type aren't Size => 1. So Naturally I would want to create a new type to handle this which I tried in the form of, creating my own String type and setting the size => 1 but Character requires 8-Bits. What data type would I need to create to read in a line of data and convert it such that it fits into a Bit_Array? I might be approaching this wrong but its very confusing for me. Any help or tips is appreciated!

2

There are 2 answers

6
Simon Wright On BEST ANSWER

You can’t use Unchecked_Conversion because, as you’ve found, a Character doesn’t correspond to a Bit. The 8-bit ASCII Character corresponding to 0 has a bit pattern corresponding to the decimal value 48, and 1 has value 49.

I think you’ll have to bite the bullet and loop through the input string. A simple function to do this, requiring that the input string consists only of 0s and 1s, might be

function To_Bit_Array (S : String) return Bit_Array is
   Input : constant String (1 .. S'Length) := S;
   Result : Bit_Array (1 .. S'Length);
begin
   for J in Input'Range loop
      Result (J) := (case Input (J) is
                        when '0' => 0,
                        when '1' => 1,
                        when others => raise Constraint_Error);
   end loop;
   return Result;
end To_Bit_Array;

(the point of the declaration of Input is to make sure that the index J is the same for both arrays; the first index of a String only has to be Positive, i.e. greater than 0).

0
B. Moore On

You could use Unchecked_Conversion however, if you are converting from say a modular integer type to a bit array. I generally avoid the use of Unchecked_Conversion unless as a last resort, but I can see the temptation to convert from an integer to a bit array type. However, I'd also consider whether to just use the modular integer, since it can be used for bitwise operations also. Using array syntax for accessing the bits is a nice feature, though. Even if I wanted to convert a modular integer to a bit array, I'd still recommend avoiding the use of Unchecked_Conversion, or pretty much Unchecked_ anything in Ada, when possible. One reason is that Unchecked_Conversion will likely be less portable. Different compilers and different targets might store the bits in different orders, for example.

Another suggestion would be to use a packed array of Booleans, rather than a packed array of bits. The storage in memory should look the same, but I think you'll find it is more convenient to work with Booleans, rather than bits. It saves you from having to compare the values to 1 or 0.

So one way to convert your input strings to a modular integer is to use Ada.Text_IO.Modular_IO package.

type Short is mod 2**16;

package Short_IO is new Modular_IO (Num => Short);

type Bit_Array is array (Positive range <>) of Boolean
  with Component_Size => 1;
...

declare
     Input : constant String := Get_Line;
     Value : Short := 0;
     Last  : Positive;
     Bits_11 : Bit11;

     -- Instead of using Unchecked_Conversion, consider writing a 
     -- simple conversion function
     --
     function To_Bit11 (Value : Short) return Bit11 is
        ((for I in Bit11'Range => (Short(2**(I-1)) and Value) /= 0));

  begin

     --  Enclosing the input string with 2#{binary_string}#, 
     --  causes the number to be read in base 2, as a binary text
     --  string.
     Short_IO.Get (From  => "2#" & Input & "#",
                   Item  => Value,
                   Last  => Last);

     --  You could use unchecked conversion here to convert
     --  to the Bit array, but I'd recommend doing this
     --  without using unchecked features of the language
     --  e.g. using a simple loop

     for I in Bits_11'Range loop
        Bits_11 (I) := (Short(2**(I-1)) and Value) /= 0;
     end loop;

     --  or you can even try an Ada 2020 feature that already has
     --  been implemented in the latest community edition of GNAT.
     --  By assigning a new form of array aggregate to the 
     --  bit array object. You'd need to set the -gnatX compiler
     --  option to tell the compiler to use Ada extended 2020
     --  features. Many Ada 2020 features are not yet available to try
     --  out, but this one is available.

     Bits_11 :=
        (for I in Bits_11'Range => (Short (2**(I-1)) and Value) /= 0);

     -- or combine this into an expression function like above,
     -- then you have a function similar to the one you'd get using
     -- Unchecked_Conversion, except more portable, and one can
     -- examine the code to understand the implementation.

     Bits_11 := To_Bit11 (Value);