Checking validity of enum value image without exception

Just double checking: is there any way to ascertain that a string can be converted to an enumeration value that does not involve raising an exception, i.e.:

begin
   return Some_Enum'Value ("asdf");
exception
   --  Invalid value, do whatever you must.
end;

I don’t think so as 'Valid takes a variable of the type, but I might be missing something?

If the enumeration only has a few values, consider getting the Image of each and comparing them to the string under consideration, after stripping it of extra whitespace and whatnot.

You would need an error enumeration in that case.

You could try to do a conversion and return true if successful and false on exception, that means you’d also need to do it twice unless you return the enum as an out parameter.

This is one area where an option type would be handy.

You can make a constant indefinite map with a key type of string and element type the enumeration. Then you can call .Contains() with a forced to upper on the input for a Boolean result.

It takes some scaffolding like a custom “&” operator to use to make the constant map and overriding contains to force the to upper but it is not terrible to do. I’ve done it for a compiler keyword search before.

If the enumeration is simple then you just insert the image of the enumeration value as the key

EDIT: Example:

    generic
        type Enumeration is (<>);
    package Enumeration_Search is 
    
        package Maps is new Ada.Containers.Indefinite_Hashed_Maps
            (Key_Type        => String,
             Element_Type    => Enumeration,
             Hash            => Ada.Strings.Hash,
             Equivalent_Keys => Standard."=");
             
        type Map is new Maps.Map with null record;
        
        type Option(Valid : Boolean := False) is record
            case Valid is
                when False => null;
                when True  => Value : Enumeration := Enumeration'First;
            end case;
        end record;
        
        overriding 
        function Contains(Self : Map; Key : String) return Boolean;
        function Find(Self : Map; Key : String) return Option;
        
        function Make return Map;
        
        function "&"(L : Map; R : Enumeration) return Map;
        
        Empty_Map : constant Map := (Maps.Empty_Map with null record);
             
    end Enumeration_Search;

body file

    package body Enumeration_Search is

        function Contains(Self : Map; Key : String) return Boolean is
            use Ada.Characters.Handling;
        begin
            return Maps.Map(Self).Contains(To_Upper(Key));
        end Contains;
        
        function Find(Self : Map; Key : String) return Option is
            use Ada.Characters.Handling;
            Location : constant Maps.Cursor := Self.Find(To_Upper(Key));
            
            use type Maps.Cursor;
        begin
            if Location = Maps.No_Element then
                return (Valid => False);
            else
                return (Valid => True, Value => Self(Location));
            end if;
        end Find;
        
        function Make return Map is
        begin
            return Result : Map do
                for Value in Enumeration'Range loop
                    Result.Insert
                    (Key      => Value'Image,
                     New_Item => Value);
                end loop;
            end return;
        end Make;
        
        function "&"(L : Map; R : Enumeration) return Map is
        begin
            return Result : Map := L do
                Result.Insert
                    (Key      => R'Image,
                     New_Item => R);
            end return;
        end "&";
        
    end Enumeration_Search;

example:

    type My_Type is (Abc, Def, Ghi);
    
    package My_Type_Search is new Enumeration_Search(My_Type);
    use type My_Type_Search.Map;

    Full_Search : constant My_Type_Search.Map := My_Type_Search.Make;
    
    Constrained_Search : constant My_Type_Search.Map := My_Type_Search.Empty_Map
        & Abc 
        & Ghi;
1 Like

Something like that didn’t even occur to me. It seems a little heavyweight for something like this, but if he’s willing to do something like that, he could always use a trie for the mapping. I have a library he can use, if he’d be interested.

(for some E in Some_Enum => E'Image = To_Upper (Potential_Value) )
1 Like

Thanks for all the comments, people, and the creativity, but I was hoping there was some simple, ARM-intended way, such a 'Valid attribute that took a string, that I had overlooked.

I think the ARM more often than not tends to go the way of exception propagation for more things than I prefer (but I primarily code in bare metal contexts, so exceptions are less useful to me).

Arnaud Charlet points out that GNAT has a 'Valid_Value attribute function that does what you want:

https://docs.adacore.com/gnat_rm-docs/html/gnat_rm/gnat_rm/implementation_defined_attributes.html#attribute-valid-value

2 Likes

I keep telling people, you can use unconstrained arrays for this…

Let me show you how:

Generic
   Type Element is (<>);
Package Generic_Optional is
   Subtype Index is Boolean range True..True;
   Type Optional is Array(Index range <>) of Element;

   Function From_String( Object: String ) return Optional;
End Generic_Optional;

Package Body Generic_Optional is
   Function From_String( Object: String ) return Optional is
   Begin
      Return Optional'( True => Some_Enum'Value (Object) );
   Exception
      Return Optional'( True..False => <> ); -- Null array
   End From_String;
End Generic_Optional;

Granted, you can’t use this technique on unconstrained elements; but otherwise it does confer a big advantage: considering optional as an unconstrained array of 0 or 1 elements guides you to using for-loops on arrays for the processing, allowing you to use other unconstrained arrays [0…N] within the system w/o a [major] rewrite.

2 Likes