Ada - how to initialize a limited tagged type that contains File_Type members? - oop

In the following code Mix_Card_Reader inherits from Mix_IO_Device, the latter being an abstract tagged record.
Previously it contained one Positive and two Stream_Access members. I'd like to alter the code so that it uses File_Type members instead.
The reason for that is that I want each instance of this type to be able to open and close its files as and when required, or not at all if need be.
The problem is that I cannot initialise this inheriting type because File_Type is a limited type. How can I write my Create_Mix_Card_Reader function to allow this?
.ads...
type Mix_IO_Device is abstract tagged limited
record
Block_Size : Positive;
Input_File : File_Type;
Output_File : File_Type;
end record;
type Mix_Card_Reader is new Mix_IO_Device with null record;
.adb...
function Create_Mix_Card_Reader return Mix_IO_Device_Access is
Ret : Mix_IO_Device_Access := new Mix_Card_Reader'(16, null, null);
begin
return Ret;
end Create_Mix_Card_Reader;
GNAT is complaining that I cannot pass null, null into the pair of File_Type members because they are not compatible of course, the nulls are a left-over from when this used to have Stream_Access members. It seems that I have to pass something in here but I don't want to have to prematurely open the files simply to placate the compiler.
What to do?
Edit:
I have a couple of obvious options:
use access File_Type instead (but I still have to maintain the opening/closing of the files elsewhere).
store all the File_Type objects in an array separately and just refer to them using Streams as before but this seems messy.

This should do the trick:
function Create_Mix_Card_Reader return Mix_IO_Device_Access is
Ret : Mix_IO_Device_Access := new Mix_Card_Reader'(
16, Input_Type => <>, Ouptut_Type => <>);
begin
return Ret;
end Create_Mix_Card_Reader;
The box notation is a placeholder for the default value. You need at least Ada 2005 to use it in aggregates and must not use positional notation, details are explained in the Ada 2005 Rationale. You can shorten the two assignments to others => <> if you want.

You don’t really need to initialize the File_Type variables, since they start off initialized (but not opened).
I got the impression that you didn’t start off using an access type? Try this (not an answer to the question as posed, but may still be useful):
with Ada.Text_IO; use Ada.Text_IO;
package Wossname is
type Mix_IO_Device is abstract tagged limited
record
Block_Size : Positive;
Input_File : File_Type;
Output_File : File_Type;
end record;
type Mix_Card_Reader is new Mix_IO_Device with null record;
function Create_Mix_Card_Reader return Mix_IO_Device'Class;
end Wossname;
I’m not 100% sure of the exact legality here, but I think this is "initializing in place":
package body Wossname is
function Create_Mix_Card_Reader return Mix_IO_Device'Class is
begin
return Ret : Mix_Card_Reader do
Ret.Block_Size := 16;
end return;
end Create_Mix_Card_Reader;
end Wossname;
and as you can see it compiles (and runs!) OK.
procedure Wossname.Test is
Reader : Mix_IO_Device'Class := Create_Mix_Card_Reader;
begin
begin
Create (Reader.Output_File, Name => "wossname.out", Mode => Out_File);
exception
when Use_Error =>
Open (Reader.Output_File, Name => "wossname.out", Mode => Out_File);
end;
Put (Reader.Output_File, "hi!");
Close (Reader.Output_File);
end Wossname.Test;

Related

How to make size of generic type explicit in Ada?

I am trying to compile this code: https://github.com/RanaExMachina/ada-fuse
Unfortunately when building I get this error:
fuse-system.ads:147:04: size clause not allowed for variable length type
This seems to be a problem because in the code it tries to set the size of a record which has a generic type as an entry. This seems to be a new error as the developer didn't had that problem back when he wrote that 2.5 years ago. Unfortunately he isn't able to help me on short notice but I have to get that library going. I am a bit helpless in fixing this issue however.
Basically it seems to me that I have to somehow tell gnat how big that type is going to be, which - contrary to gnat's believe - is a priori knowable: it is an access type. Either in the record or the generic type definition.
The relevant parts are:
fuse-main.ads:
package Fuse.Main is
package IO is
new Ada.Direct_IO (Element_Type);
type File_Access is access IO.File_Type;
fuse-system.ads:
generic
type File_Access is private;
package Fuse.System is
...
type File_Info_Type is record
Flags : Flags_Type;
Fh_Old : Interfaces.C.unsigned_long;
Writepage : Interfaces.C.int;
Direct_IO : Boolean := True;
Keep_Cache : Boolean := True;
Flush : Boolean := True;
Nonseekable : Boolean := True;
Fh : File_Access;
Lock_Owner : Interfaces.Unsigned_64;
end record;
type File_Info_Access is access File_Info_Type;
pragma Convention (C, File_Info_Type);
for File_Info_Type'Size use 32*8;
My gnat version is: 4.9.2-1 (debian jessie)
You know that File_Access is an access type, but within Fuse.System the compiler doesn’t; all it knows is that it’s definite and supports assignment and equality. The actual could be hundreds of bytes.
To tell the compiler that it is an access type, try something like this (I compressed it into one package for my convenience, on Mac OS X, hence the 64-bit pointer size; it compiles OK):
with Ada.Text_IO;
package Fuse_Tests is
generic
type File_Type is limited private;
type File_Access is access File_Type;
package Fuse_System is
type File_Info_Type is record
Fh : File_Access;
end record;
for File_Info_Type'Size use 64;
end Fuse_System;
type File_Access is access Ada.Text_IO.File_Type;
package My_Fuse_System is new Fuse_System
(File_Type => Ada.Text_IO.File_Type,
File_Access => File_Access);
end Fuse_Tests;
Or, an alternative suggested in the comments:
with Ada.Text_IO;
package Fuse_Tests is
generic
type File_Type;
package Fuse_System is
type File_Access is access File_Type;
type File_Info_Type is record
Fh : File_Access;
end record;
for File_Info_Type'Size use 64;
end Fuse_System;
package My_Fuse_System is new Fuse_System
(File_Type => Ada.Text_IO.File_Type);
-- if needed ...
subtype File_Access is My_Fuse_System.File_Access;
end Fuse_Tests;

Why is the type of an object a pointer to tagged typed, and not a tagged type?

I have 3 files: other.ads, other.adb, and test.adb.
other.ads:
package Other is
type Thing is tagged record
Stuff : String(1..4);
end record;
procedure Say (T : in Thing);
end Other;
other.adb: not shown for brevity, and not necessary for the example.
test.adb:
with Other;
procedure Test is
T : Other.Thing := new Other.Thing;
begin
T.Stuff := "test";
T.Say;
end Test;
I get this error:
test.adb:4:23: expected type "Thing" defined at other.ads:2
test.adb:4:23: found type access to "Thing" defined at line 4
If I have these files instead:
other.ads:
package Other is
type Thing is tagged record
Stuff : String(1..4);
end record;
type Ref is access all Thing;
procedure Say (T : in Thing);
end Other;
test.adb:
with Other;
procedure Test is
T : Other.Ref := new Other.Thing;
begin
T.Stuff := "test";
T.Say;
end Test;
Then it compiles and runs fine.
Why can't I specify new Other.Thing to be of type Other.Thing?
If you declare a variable in Java, the way you set it up depends on whether the variable’s type is primitive. int foo reserves space for an integer. Thing foo, on the other hand, reserves space for a reference (pointer) to a Thing, and you use new to reserve space for the Thing itself; Thing foo = new Thing.
Ada isn’t like that (nor is C or C++, for that matter); when you say Foo : Thing, the compiler reserves space for the Thing right there (probably on the stack). So your first example can just read
T : Other.Thing;
begin
T.Stuff := “test”;
The time when you use the new keyword in Ada is when you need an access value for some reason, as you have forced in your second example; you’ve declared T as Ref, which is declared as access all Thing.
Note that in your second example, when you say
T.Stuff := “test”;
this is actually shorthand for
T.all.Stuff := “test”;
and some people like to put the .all in explicitly.
The type of T is an access type that points to an object of type Other.Thing because you specified a allocator using new. You can also simply declare an object of type Other.Thing an initialize it using an aggregate.
with Ada.Text_IO;
procedure Test is
package Other is
type Thing is tagged record
Stuff : String(1..4);
end record;
end Other;
S : Other.Thing := (Stuff => "test");
T : access Other.Thing := new Other.Thing;
begin
Ada.Text_IO.Put_Line(S.Stuff);
T.Stuff := "test";
Ada.Text_IO.Put_Line(T.Stuff);
end Test;

Upcasting accesses

Let's say I have a Planet:
type Planet is tagged null record;
type Planet_Ref is access Planet'class;
Now I subclass it:
type Habitable_Planet is new Planet with null record;
type Habitable_Planet_Ref is access Habitable_Planet'class;
Now I define some variables:
p: Planet_Ref := Make_Planet;
hp: Habitable_Planet_Ref := Make_Habitable_Planet;
I would naively expect that assigning p := hp would work, because a Habitable_Planet is a subclass of Planet. But of course that won't work because every type defined with type is distinct and doesn't interoperate with any other type.
So I'd expect to have to declare Habitable_Planet_Ref to be a subtype of Planet_Ref to make this work. But the syntax doesn't seem to allow for this.
How do I make this work?
(Yes, I know I can use an explicit view conversion to cast a Habitable_Planet_Ref to a Planet_Ref, but that's really ugly and I'd like to avoid it.)
Ada recognizes types by name, so indeed you would need a view conversion here.
But if you are using Ada 2005, you can use anonymous access types instead. For instance:
hp: access Habitable_Planet'Class := Make_Habitable_Planet;
p: access Planet'Class := hp; -- valid with anonymous access types
One the drawbacks of using anonymous access types is that the code is more
verbose (although in general you would not use them for local variables, but
as parameters to subprograms or as fields in a (tagged) record.
They also can't be used with Unchecked_Deallocation. In fact, I personally often
use them exactly because of that: when I have a field in a record which is of an
anonymous access type, I know that the record does not "own" the accessed data,
and therefore it should not free it (in fact, I would have to write some convoluted
code to free them).
And of course as per your request the result for type matching are slightly more
relax, which is nice too.
ajb is correct in his comment. Ada is too strict for many practices you might be used to in other languages. An alternative would be to just not use objects and instead just simple records or discriminate records. I understand this may not be what you are looking for, but from my experience more can be done with less lines of code and the solution tends to me easier to understand.
Simple record
--...
type Rec_Planet is record
--.. stuff
end record;
--...
type Rec_Habitable_Planet is record
Planet : Rec_Planet := (others => <>);
--.. stuff
end record;
Discriminate record
type Enum_Planet is (Normal_Planet, Habitable_Planet);
type Rec_Planet(Kind : Enum_Planet := Normal_Planet) is record
-- rec_Planet stuff..
case Kind is
when Habitable_Planet => -- Rec_Habitable_Planet stuff
when others => null;
end case;
end record;
So #manuBriot gave me the answer I needed, but there were some other things I was doing wrong in my question which I should clarify because they'll confuse anyone else reading this question.
I was confusing the issue by using accesses. From Ada's point of view all accesses defined with type are distinct, so it never gets as far as looking at what the access is pointing at; it just disallows the assignment.
However, Ada does support implicit upcasting of class-wide types (and also discrete types, where an instance of a subtype will get implicitly cast to its parent type --- implement ALL the class hierarchies! But that's not really relevant here.) Example here:
With Ada.Text_IO; Use Ada.Text_IO;
With Ada.Integer_Text_IO; Use Ada.Integer_Text_IO;
procedure Prog is
package Superclass is
type Class is tagged record
null;
end record;
procedure Announce(self: in out Class);
subtype Var is Class'class;
end;
package body Superclass is
procedure Announce(self: in out Class)
is
begin
Put_Line("I am the superclass");
end;
end;
package Subclass is
type Class is new Superclass.Class with null record;
procedure Announce(self: in out Class);
end;
package body Subclass is
procedure Announce(self: in out Class)
is
begin
Put_Line("I am the subclass");
end;
end;
osuper: Superclass.Class;
osub: Subclass.Class;
vsuper: Superclass.Var := osuper;
vsub: Superclass.Var := osub; -- implicit upclass here
begin
vsuper.Announce;
vsub.Announce;
end;
(It's in ideone here: http://ideone.com/M79l0a Interesting sidenote. If you define subtype Var is Superclass.Var in the Prog package, and then use Var in the definitions of vsuper and vsub, ideone's Ada compiler crashes.)
Of course, like all indefinite types, once the variable has been initialised then its type cannot be changed. So I can assign any Subclass.Object to vsub, but I can't assign a Superclass.Object to it. And of course I'm physically copying the object rather than referring to an object elsewhere.
Implicitly upcasting accesses to class-wide types should be safe. Because assigning to a class-wide type does a runtime instance check to make sure that the physical type of the objects are compatible, it ought not to be possible to accidentally corrupt objects like you can in C++ --- see Overwriting an instance of a subclass with an instance of a superclass, for example. Therefore assigning to a dereferenced access shouldn't be a problem. However, it's 2100 at night and my brain has turned to sludge, so it's entirely possible that I'm missing something here. (Although given that when using anonymous accesses there aren't any problems, I suspect not.) Elucidation welcome...

typeof Equivalent for Object Types in PL/SQL

I'm trying to use OOP and TDD inside of Oracle. Yes, I know that sounds crazy. And, I need some advice.
I'm writing a test for the following constructor method (simplified for the purposes of this question):
CONSTRUCTOR FUNCTION PERSON(p_pidm NUMBER, p_throw_exception NUMBER DEFAULT 0, p_program_id NUMBER DEFAULT 0)
RETURN SELF AS RESULT IS
BEGIN
-- Attach constructor parameters to internal attributes
self.throw_exception := p_throw_exception;
self.program_id := p_program_id;
-- TESTING STUDENT INSTANTIATION
self.student := NEW STUDENT(self.a_pidm);
RETURN;
END;
In the corresponding test, I'll need to verify that self.student is set to a valid instance of STUDENT. In other languages, I do this with a typeof method, but I'm not aware of one in PL/SQL.
So, the question is, does anyone know a function/procedure that I can pass a user-defined type into and get back its class/type name?
Thanks.
You probably want to use the IS OF <<type>> syntax.
Something like
IF l_variable IS OF( student )
THEN
<<do something>>
END IF;

Ignore Ada Function Return Values

Is there a way to ignore return values in Ada functions?
I have a function which imports from an Intrinsic.
subtype int32 is Interfaces.Interger_32;
function Intrinsic_Sync_Add_And_Fetch
(P : access int32; I : int32) return int32;
pragma Import(
Intrinsic,
Intrinsic_Sync_Add_And_Fetch,
"__sync_add_and_fetch_4");
If I want to use this in a procedure, I need to accept the return value or I will get a compiler error:
cannot use function Intrinsic_Sync_Add_And_Fetch in procedure call.
But, if I create a variable that simply takes the return value of the function and is never used then I get compiler warnings. Obviously, I'd rather avoid those.
I can't very well assign the value back to the value I'm adding to; this would undermine the point of the add operation being atomic.
There is the option of taking the value and doing something with it, like:
val := Intrinsic_Sync_Add_And_Fetch(...);
if val := 0 then null; end if;
It forces the code to compile without errors or warnings, but it seems stupid to me. How can I "get around" this language feature and safely ignore the return value?
Edit: What is __sync_add_and_fetch_4?
This is a built-in atomic operation available on Intel CPUs. As such, part of my Autoconf/Automake process would be deciding if the operation is available, and use a fallback implementation, which involves a critical section, if it's not.
You can read about this and similar operations in GCC's section on atomic builtins.
The __sync_add_and_fetch_4 does pretty much exactly what it says. In C, it would look something like this:
int32_t __sync_add_and_fetch_4(int32_t *ptr, int32_t value) {
*ptr += value;
return *ptr;
}
So it's an atomic addition operation, which returns the result of the addition. Basically, it's an atomic += operator. The _4 means that it takes a 4-byte integer.
Edit: I understand that I could probably just switch off that particular compiler warning, but that always feels dirty to me. If there's a solution available that allows me to continue using -Wall -Werror then I'd love to see it.
declare
dummy : constant return_type := my_function;
pragma Unreferenced (dummy);
begin null; end;
or write a wrapper procedure.
If you never want to reference the return value, why not declare the subprogram as a procedure? The value is going to be returned in a register, so throwing it away won’t cause a lot of grief. (I stand to be corrected on this one!)
subtype int32 is Interfaces.Integer_32;
procedure Intrinsic_Sync_Add_And_Fetch
(P : access int32; I : int32);
pragma Import(
Intrinsic,
Intrinsic_Sync_Add_And_Fetch,
"__sync_add_and_fetch_4");
You said you're only targeting the GNAT compiler. The GNAT User's Guide says:
Note that a special exemption applies to variables which contain any of the substrings DISCARD, DUMMY, IGNORE, JUNK, UNUSED, in any casing. Such variables are considered likely to be intentionally used in a situation where otherwise a warning would be given, so warnings of this kind are always suppressed for such variables.
So the simplest solution to your problem is :
unused := Intrinsic_Sync_Add_And_Fetch(...);
Though you might want to wrap that in a procedure if you are going to use it more than a couple of times :
procedure Intrinsic_Sync_Add_And_Fetch(P : access int32; I : int32) is
unused : int32;
begin
unused := Intrinsic_Sync_Add_And_Fetch(P : access int32; I : int32);
end Intrinsic_Sync_Add_And_Fetch;
i don't know of any way to ignore the return value of a function in Ada: the language has been especially designed to force you to store those return values.
personally, i would store the return value and ignore any warning regarding the use of the variable. anyway, the said warning is quite strange since the variable is indeed used to store the return value.