Jerunh
Jerunh

Reputation: 514

Ada Generic Package with Default Subprogram

I am trying to create an Ada generic package that has a subprogram parameter with a default value. I cant get the compiler to recognize the default value.. Im guessing this is due to visibility. Is there a way to forward declare a function within the generic declaration?

The Generic Spec:

generic
    type Item is private;
    type Item_Ref is access all Item;
    Addr : System.Address;
    Default : Item;

    with Is_Valid (Obj : Item) return Boolean;

    -- Forward Declare ** DOES NOT COMPILE
    function Default_Validate (Data_Ptr : Item_Ref) return Boolean;

    with function Validate (Data_Ptr : Item_Ref) return Boolean is Default_Validate;

package Foo is

    -- function Default_Validate (Data_Ptr : Item_Ref) return Boolean;

    function Read_Eeprom return Item;

end Foo;

The Generic Body:

package body Foo is

    Obj : aliased Item;
    for Obj'Address use Addr;

    -- Read Method
    function Read_Eeprom return Item is
    begin

        -- ** Read EEPROM using OBJ **

        Validate (Obj'Unchecked_Access);

    end Read_Eeprom;

    -- Default Validate Method
    function Default_Validate (Data_Ptr : Item_Ref) return Boolean is 
        Valid : Boolean;
    begin
        Valid := Is_Valid(Data_Ptr.all);

        if not Valid then
            Data_Ptr.all := Default;
        end if;

        return Valid;
    end Default_Validate;

end Foo;

Driver

with Foo;
procedure Main is
    MAX_INT : constant Integer := 100;
    MIN_INT : constant Integer := 0;

    -- Special / Non-Scaler Type
    type Pair_Type is 
        record
            X : Integer;
            Y : Integer;
        end record;

    type Pair_Ref is access all Pair;

    -- Is Valid
    function Int_Is_Valid(Int : Integer) return Boolean is
    begin 
        return (Int <= MAX_INT and Int >= MIN_INT);
    end Pair_Is_Valid;

    -- Is Valid
    function Pair_Is_Valid(Pair : Pair_Type) return Boolean is
    begin 
        return Pair.X'Valid and Pair.Y'Valid;
    end Pair_Is_Valid;

    -- Validate
    function Pair_Validate(Pair : Pair_Ref) return Boolean is
        Valid : Boolean := True;
    begin
        if not Pair.X'Valid then
            Pair.X := 0;
            Valid := False;
        end if;

        if not Pair.Y'Valid then
            Pair.Y := 0;
            Valid := False;
        end if;

        return Valid;
    end Special_Validate;

    type Int_Ref is access all Integer;

    My_Int  : Integer;
    My_Pair : Pair_Type;
    Default_Pair : Pair_Type := (0,0);

    package Int_Obj is new Foo (Item => Integer,
                                Item_Ref => Int_Ref,
                                Addr => My_Int'Address,
                                Default => 0,
                                Is_Valid => Int_Is_Valid);

    package Pair_Obj is new Foo (Item => Pair_Type,
                                 Item_Ref => Pair_Ref,
                                 Addr => My_Pair'Address,
                                 Default => Default_Pair,
                                 Is_Valid => Pair_Is_Valid,
                                 Validate => Pair_Validate);

   Tmp_Int   : Integer;
   Tmps_Pair : Pair_Type;

begin

   Tmp_Int := Int_Obj.Read_Eeprom;
   Tmp_Pair := Pair_Obj.Read_Eeprom;

end Main;

The error Im getting is "end of file expected, file can only have one compilation unit" How can I default a generic subprogram to a function that is a member of the package?

Upvotes: 1

Views: 2228

Answers (4)

Shark8
Shark8

Reputation: 4198

You really keep moving the goalposts. Your new additions are, in a word, terrible: they don't compile at all and they are a smattering of obviously cut-and-pasted code as evidenced by mismatching function-names (e.g. Int_Is_Valid/Pair_Is_Valid).

First, let's use a signature-package.

signature.ads

generic
    Type Item is private;
    Default : in Item;
package SIGNATURE is
end SIGNATURE;

foo.ads

with
System,
SIGNATURE;

generic
    with package Item_Pkg is new SIGNATURE(<>);

    Addr     : System.Address;

    with function Is_Valid(X : Item_Pkg.Item) return Boolean is <>;
package Foo is
    use Item_Pkg;

    function Read_Eeprom return Item;
    function Is_Valid (Data_Ptr : access Item) return Boolean;

private
    Port : Item;    
    pragma Volatile( Port );
    Pragma Import( Convention => Ada, Entity => Port );

    For Port'Address Use Addr;
end Foo;

foo.adb

package body Foo is

    function Read_Eeprom return Item is
        Result : constant Item:= Port;
    begin
        if Is_Valid(Result) then
            return Result;
        else
            return Default;
        end if;
    end Read_Eeprom;

    function Is_Valid (Data_Ptr : access Item) return Boolean is
    begin
        return Is_Valid(Data_Ptr.all);
    end Is_Valid;

end Foo;

driver.ads

package Driver is
    MAX_INT                : constant Integer := 100;
    MIN_INT                : constant Integer := 0;

    -- Special / Non-Scaler Type
    type Pair_Type is 
    record
        X                  : Integer;
        Y                  : Integer;
    end record;

    -- Is Valid **USING OVERLOADS**
    function Is_Valid(Int  : Integer  ) return Boolean;
    function Is_Valid(Pair : Pair_Type) return Boolean;

    My_Int                 : Integer;
    My_Pair                : Pair_Type;

private
    Default_Pair           : constant Pair_Type := (0,0);
    Default_Integer        : constant Integer   := 0;
end Driver;

driver.adb

with
Foo,
SIGNATURE;

package body Driver is

    -- Is Valid
    function Is_Valid(Int  : Integer)   return Boolean is
        (Int <= MAX_INT and Int >= MIN_INT);

    function Is_Valid(Pair : Pair_Type) return Boolean is
        (Pair.X'Valid and Pair.Y'Valid);


    package Int_pkg  is new SIGNATURE(Integer,   0);
    package Pair_Pkg is new SIGNATURE(Pair_Type, Default_Pair);

    -- Using defaults for Is_Valid.
    package Int_Obj is new Foo (Item_Pkg  => Int_Pkg,
                                Addr      => My_Int'Address
                               );
    package Pair_Obj is new Foo(Item_Pkg => Pair_Pkg,
                                Addr     => My_Pair'Address
                               );

end Driver;

Given the rather obvious structuring of your code around access-types, I'm guessing that you are trying to "import" knowledge from C or C++. That's going to cause you a lot of trouble/work later on if you try using Ada like it's a C-style language.

Also, it may be beneficial to sit down, take a breath, and think about things in terms of types both the problem-space and, separately, the architecture.

Upvotes: 1

Shark8
Shark8

Reputation: 4198

I'm not sure this works either. It would seem I would have to define a validate function for each instance of a generic even if I'm not passing it in as a parameter.

That is probably the best way to approach this; you're talking validation of data after all... and defaulting that could have unexpected behavior. (Any type that's crossing in/out of your system ought to be validated; e.g. File reads, DB reads, user-input, etc.)

The point of having the default function in the generic was to eliminate duplicate code. Any other thoughts?

Well, there is one method that springs to mind. It requires us to limit what we accept in our generic though. My other solutions are [almost] fully generalized and therefore would work on any non-limited type.

Taking the attribute 'Valid we see that the documentation says this:
The Valid attribute can be used to check the validity of data produced by unchecked conversion, input, interface to foreign languages, and the like.

So we have a default validation, of sorts.
We also have a way to have attributes stand in for the default of function formal-parameters.
We have a way to default to visible functions.
Last we have nested generics.

Now there's no generic formal type scalar, which would be useful here... but we have a way to limit the type of a formal parameter Type T(<>) is (<>) is limited to integral numerics, modular-types, and enumerations... all of which we know are scalar-types.

Generic
    Type Item(<>) is (<>);
Package Generic_Base is
    -- To use this as a Ada-95 pkg delete everything after 'Boolean',
    -- create a body and return X'Valid from the implementation.
    Function Default_Validate(X : Item) Return Boolean is (X'Valid);

    Generic
        with function Validate(X:Item) return Boolean is Default_Validate;
    Package Generic_Nested is
        -- Operations requiring validation.
    End Generic_Nested;

End Generic_Base;

Using these packages would be as follows:

Package Base is new Generic_Base( Integer );
Package Nested is new Base.Generic_Nested;

If you're happy in limiting the formal parameter to integrals/modulars/enumerations, this should work.

So, there you go.


Recommended reading:

Ada's Generic Formal Type System


No syntax highlighting because it looks terrible on this.

Upvotes: 1

Shark8
Shark8

Reputation: 4198

As you have it, the generic is defining a function, Default_Validate, because keyword function is not preceded by with. What you should have is this:

generic
    type Item is private;
    type Item_Ref is access all Item;

    with function Default_Validate (Data_Ptr : Item_Ref) return Boolean;
    -- A function "Validate", which defaults to 'Default_Validate'.
    with function Validate (Data_Ptr : Item_Ref) return Boolean is Default_Validate;
package Foo is
    -- function Default_Validate (Data_Ptr : Item_Ref) return Boolean;
end Foo;

EDIT:

The comments clarified that the previous was not what you want. The form above would be used in cases where you have a possibly-overriding validator and a default one w/o having to resort to using tagged types (Ada's terminology for instances of OOP classes).

Instead what you want is for the parameters to possibly take subprograms from the visible subprograms as defaults for the formal parameters, the following does so:

generic
    type Item is private;
    type Item_Ref is access all Item;

    -- A function "Validate", which defaults to 'Validate'. The function
    -- needs to be visible when the generic is instantiated, not here
    -- where the generic is defined.
    with function Validate (Data_Ptr : Item_Ref) return Boolean is <>;
package Foo is
    -- Just a stub.
end Foo;

    Type Some_Integer_Access is access all Integer;
    function Validate (Data_Ptr : Some_Integer_Access) return Boolean is (true);


    Package K is new Foo( Item     => Integer,
                          Item_Ref => Some_Integer_Access
            );

But even this might be improved:

generic
    type Item is private;
    with function Validate (Data_Ptr : not null access Item) return Boolean is <>;
package Foo is
    -- Just a stub.
end Foo;


    function Validate (Data_Ptr : not null access Integer) return Boolean is (true);

    -- One parameter! Ah! Ah, Ah!
    Package K is new Foo( Item => Integer );

Upvotes: 1

ajb
ajb

Reputation: 31699

Unfortunately, you can't--it's a chicken-and-egg problem. The compiler needs to figure out what all the generic parameters are going to be before it can instantiate the generic; but the Default_Validate method will not become available until after the generic is instantiated. The closest I think you can come is to declare two generics:

generic
    type Item is private;
    type Item_Ref is access all Item;
    with function Validate (Data_Ptr : Item_Ref) return Boolean;   
package Foo is

    function Default_Validate (Data_Ptr : Item_Ref) return Boolean;
    -- etc.

end Foo;

generic
    type Item is private;
    type Item_Ref is access all Item;
package Foo_With_Default_Validator is
    -- important procedure/function declarations from Foo
end Foo_With_Default_Validator;

package body Foo_With_Default_Validator is
    function Default_Validate (Data_Ptr : Item_Ref) return boolean;
    package My_Foo is new Foo(Item, Item_Ref, Default_Validate);  
    function Default_Validate (Data_Ptr : Item_Ref) return boolean
        renames My_Foo.Default_Validate;
    -- and other procedures/functions will be renames of things from My_Foo
end Foo_With_Default_Validator;

(I haven't tested this yet. EDIT: tested, compiles OK.) I'm assuming here that the only publicly visible things in Foo are procedures and functions. If there are other important features (such as types), it gets more complicated, and then you might have to use nested generics, where with function Validate gets moved off the outer generic into an inner generic, or you might be able to use a generic formal package to split the generic into two parts. In either of those cases, the user of the generic might have to perform two instantiations. If the above solution works, then a user will instantiate either Foo or Foo_With_Default_Validator, but it would be one or the other--two instantiations wouldn't be needed. If you need more assistance, I think we'd need to see the visible part of Foo.

EDIT 2: Here's a solution if you're willing to require an 'Access attribute when instantiating:

generic
    type Item is private;
    type Item_Ref is access all Item;
    Validate : access function (Data_Ptr : Item_Ref) return Boolean := null;   
package Foo is

    function Default_Validate (Data_Ptr : Item_Ref) return Boolean;
    -- etc.

end Foo;

Then in the body of Foo, you'll want a function like this:

function Perform_Validate (Data_Ptr : Item_Ref) return Boolean is
begin
    if Validate = null
        then return Default_Validate (Data_Ptr);
        else return Validate (Data_Ptr);
    end if;
end Perform_Validate;

and call Perform_Validate from the rest of the body whenever you want to call the validation function. (Perform_Validate could be written more concisely using new Ada 2012 features, but you get the idea.)

Upvotes: 1

Related Questions