Passing map to common procedure - ada

The Java programming languages frequently uses interfaces like java.util.Map.
In the following example two custom map packages are created by using the generic packages Ada.Containers.Hashed_Maps and Ada.Containers.Ordered_Maps. Both generic packages are offering the functions/procedures Clear and Length. The procedures Do_Something are using this functions/procedures to clear the passed map and to print the container length (stupid example ...).
I am right then it is not possible to create a procedure Do_Something_Special that would accept maps of both types Map_One.Map and Map_Two.Map? In Java it would be possible to define a parameter with the type Map<Natural, Unbounded_String>.
with Ada.Containers.Hashed_Maps;
with Ada.Containers.Ordered_Maps;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Text_IO;
procedure Main is
function Hash (Value : Natural) return Ada.Containers.Hash_Type is
begin
return Ada.Containers.Hash_Type (Value);
end Hash;
package Map_One is new Ada.Containers.Hashed_Maps
(Key_Type => Natural,
Element_Type => Unbounded_String,
Hash => Hash,
Equivalent_Keys => "=");
package Map_Two is new Ada.Containers.Ordered_Maps
(Key_Type => Natural,
Element_Type => Unbounded_String);
procedure Do_Something (Input : in out Map_One.Map) is
begin
Input.Clear;
Ada.Text_IO.Put_Line ("Length: " & Input.Length'Image);
end Do_Something;
procedure Do_Something (Input : in out Map_Two.Map) is
begin
Input.Clear;
Ada.Text_IO.Put_Line ("Length: " & Input.Length'Image);
end Do_Something;
procedure Do_Something_Special (Input : in out ???) is
begin
Input.Clear;
Ada.Text_IO.Put_Line ("Length: " & Input.Length'Image);
end Do_Something_Special;
begin
null;
end Main;

Just like in Java you can use generics or interfaces.
A generic only solution:
generic
type Map is private;
with procedure Clear(Self : in out Map);
with function Length(Self : Map) return Ada.Containers.Count_Type;
procedure Do_Something_Special(Input : in out Map);
procedure Do_Something_Special(Input : in out Map) is
begin
Clear(Input);
Ada.Text_IO.Put_Line("Length: " & Length(Input)'Image);
end Do_Something_Special;
procedure Do_Something_Map_One is new Do_Something_Special
(Map => Map_One.Map,
Clear => Map_One.Clear,
Length => Map_One.Length);
procedure Do_Something_Map_Two is new Do_Something_Special
(Map => Map_Two.Map,
Clear => Map_Two.Clear,
Length => Map_Two.Length);
If instead you want to go the route of using a Map interface you can do the following:
Create a generic interface for any key/value types
use Ada.Containers;
generic
type Key_Type is private;
type Element_Type is private;
package Map_Interfaces is
type Map_Interface is interface;
procedure Clear(Self : in out Map_Interface) is abstract;
function Length(Self : Map_Interface) return Count_Type is abstract;
-- other operations
end Map_Interfaces;
Next implement it for the key/value types you want:
package My_Map_Interfaces is new Map_Interfaces
(Key_Type => Natural,
Element_Type => Unbounded_String);
use My_Map_Interfaces;
Now you are able to use the class type of the interface to operate on any map that implements that interface:
procedure Do_Something_Special_1(Input : in out Map_Interface'Class) is
begin
Input.Clear;
Ada.Text_IO.Put_Line ("Length: " & Input.Length'Image);
end Do_Something_Special_1;
Then you just need to extend the Ada map types and implement the interface:
type Map_1 is new Map_One.Map and Map_Interface with null record;
type Map_2 is new Map_Two.Map and Map_Interface with null record;
M1 : Map_1;
M2 : Map_2;
and you can call it this way:
Do_Something_Special_1(M1);
Do_Something_Special_1(M2);
OR you an create another generic function if you want static dispatch instead of dynamic:
generic
type Map is new Map_Interface with private;
procedure Do_Something_Special_2(Input : in out Map);
procedure Do_Something_Special_2(Input : in out Map) is
begin
Input.Clear;
Ada.Text_IO.Put_Line ("Length: " & Input.Length'Image);
end Do_Something_Special_2;
procedure Do_Something_Map_1 is new Do_Something_Special_2(Map_1);
procedure Do_Something_Map_2 is new Do_Something_Special_2(Map_2);
and call it like this:
Do_Something_Map_1(M1);
Do_Something_Map_2(M2);

Related

How to get around forbidden discriminants defaults for tagged records in Ada?

I am learning Ada and I've hit a design problem. Excuse me as I'm not up with basic Ada mechanisms and idioms.
Let's say I want to represent an operation. Operators can be either plus or minus and operands can be either integers or strings.
Disclaimer: some things may not make much sense on a semantic level (minus on strings, operators without operands, ...) but it's all about representation.
For now I have the following incorrect code:
operand.ads:
package Operand is
-- I want a None for unary operands or operators without operands
type Operand_Type is (Int, Str, None);
-- This needs to be tagged
type Instance (Op_Type : Operand_Type := None) is tagged record
case Op_Type is
when Int =>
Int_Value : Integer;
when Str =>
Str_Value : String (1 .. 10);
when None =>
null;
end case;
end record;
-- Some methods on operands...
end Operand;
operation.ads:
with Operand;
package Operation is
type Operation_Type is (Plus, Minus);
-- This also needs to be tagged
type Instance is tagged record
Left, Right : Operand.Instance;
end record;
-- Some methods on operations...
end Operation;
main.adb:
with Operand;
with Operation;
procedure Main is
Op : Operation.Instance;
begin
Op.Left := (Op_Type => Operand.Int, Int_Value => 1);
Op.Right := (Op_Type => Operand.Int, Int_Value => 3);
end Main;
When I try to compile I get the following errors:
$ gnatmake main.adb
gcc -c main.adb
operand.ads:7:45: discriminants of nonlimited tagged type cannot have defaults
operation.ads:9:28: unconstrained subtype in component declaration
gnatmake: "main.adb" compilation error
I get why I can't use defaults on tagged type's discriminant but I don't really know how to get around this limitation.
Proposal 1:
Stop using variant records and use a record with one field for each operand. But I feel like this is just throwing away code elegance.
Proposal 2:
Remove defaults from Operand.Instance record and constrain Left and Right from Operation.Instance record. But I get a runtime error :
raised CONSTRAINT_ERROR : main.adb:7 discriminant check failed
As I cannot dynamically change discriminant value of a record's field.
Any help would be much appreciated !
Jim Rogers already discussed using inheritance. You can also use composition if you like by creating an internal non tagged type (which allows defaults), make the Operand.Instance type tagged private, have the private implementation use the internal non tagged version, and just add what operations you need to set and get the operands:
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello is
package Operand is
-- I want a None for unary operands or operators without operands
type Operand_Type is (Int, Str, None);
Invalid_Operand : exception;
-- This needs to be tagged
type Instance is tagged private;
function Int_Value(Value : Integer) return Instance;
function Str_Value(Value : String) return Instance;
function Null_Instance return Instance;
function Int_Value(Self : Instance) return Integer;
function Str_Value(Self : Instance) return String;
function Op_Type(Self : Instance) return Operand_Type;
-- Some methods on operands...
private
type Instance_Internal (Op_Type : Operand_Type := None) is record
case Op_Type is
when Int =>
Int_Value : Integer;
when Str =>
Str_Value : String (1 .. 10);
when None =>
null;
end case;
end record;
type Instance is tagged record
Impl : Instance_Internal;
end record;
function Int_Value(Value : Integer) return Instance is (Impl => (Int, Value));
function Str_Value(Value : String) return Instance is (Impl => (Str, Value));
function Null_Instance return Instance is (Impl => (Op_Type => None));
function Int_Value(Self : Instance) return Integer
is (if Self.Impl.Op_Type = Int then Self.Impl.Int_Value else raise Invalid_Operand);
function Str_Value(Self : Instance) return String
is (if Self.Impl.Op_Type = Str then Self.Impl.Str_Value else raise Invalid_Operand);
function Op_Type(Self : Instance) return Operand_Type
is (Self.Impl.Op_Type);
end Operand;
package Operation is
type Operation_Type is (Plus, Minus);
-- This also needs to be tagged
type Instance is tagged record
Left, Right : Operand.Instance;
end record;
-- Some methods on operations...
end Operation;
Op : Operation.Instance;
begin
Put_Line("Hello, world!");
Op.Left := Operand.Int_Value(1);
Op.Right := Operand.Int_Value(3);
Put_Line(Integer'Image(Op.Left.Int_Value));
Put_Line(Integer'Image(Op.Right.Int_Value));
end Hello;
You can break the Operand package into spec and body for better readability, this was just for example.

Using File_Type as a record component?

I'm trying to write a lexer in Ada and have run into an issue.
procedure main is
...
type lexer is tagged record
input : ada.text_io.file_type;
index : integer;
end record;
...
my_lexer : lexer;
input_file_name : bounded_string;
input_file : ada.text_io.file_type;
next_token : token;
begin
input_file_name := get_input_file;
ada.text_io.open(
file => input_file,
mode => ada.text_io.in_file,
name => to_string(input_file_name)
);
my_lexer := (input => input_file, index => 0);
next_token := my_lexer.get_next_token;
ada.text_io.put_line(to_string(next_token.text));
end main;
On the line my_lexer := (input => input_file); I get the following error:
main.adb:22:17: nonlimited tagged type cannot have limited components
I understand this to be an issue since ada.text_io.in_file is a limited type, but if I were to just remove this from the record, and instead pass it as an argument to get_next_token,
...
type lexer is tagged record
index : integer;
end record;
function get_next_token(this: lexer'class; input_file: ada.text_io.file_type) return token;
...
Then this would make the state of the lexer invalid if you were to switch the input_file between calls of get_next_token thus breaking it.
Is there a way in Ada to create something like this like you would in C?
struct lexer {
FILE *input;
int index;
};
One approach, allowing the lexer type to be tagged limited is to create the limited object in-place, so that its components (specifically the limited file_type component) aren't created elsewhere (legal) and then assigned (copied, illegal). In this case, the file component of lexer is passed to the Open call, which updates it in place.
procedure lex2 is
type lexer is tagged limited record
input : ada.text_io.file_type;
index : integer;
end record;
input_file_name : unbounded_string;
my_lexer : lexer;
-- next_token : token;
begin
input_file_name := get_input_file;
my_lexer.index := 0;
ada.text_io.open( file => my_lexer.input,
mode => ada.text_io.in_file,
name => to_string(input_file_name));
-- next_token := my_lexer.get_next_token;
-- ada.text_io.put_line(to_string(next_token.text));
end lex2;
Usually when you run into problems like this there are two solutions
Define a new type that is an access to the limited type and use it in your record, or
Make your type limited. This is maybe (in my opinion) the cleaner solution since you do not have to mess with allocation, release, and stuff. (Personally, I try to avoid access types as much as possible). Of course, making it limited will prevent you to assign it, but maybe this is not a major problem in the case of a lexer.
What I'd do is hide all these details and create an abstraction:
package Lexing is
type Info is tagged limited private;
function Is_Open (State : in Info) return Boolean;
-- Description of subprogram here
procedure Open (State : in out Info; Name : in String) with
Pre => not State_Is_Open,
Post => State.Is_Open;
-- Description of subprogram here
procedure Close (State : in out Info) with
Pre => State.Is_Open,
Post => not State.Is_Open;
-- Description of subprogram here
type Token_Info is tagged private;
function Next (State : in out Info) return Token_Info with
Pre => State.Is_Open;
-- Description of subprogram here
function Text (Token : in Token_Info) return String;
-- Description of subprogram here
...
private -- Lexing
...
end Lexing;
Obviously the fun bits are left as an exercise for the reader.
I'm not sure if this is the best solution, but this is what I've come up with:
procedure main is
type file_access is access all ada.text_io.file_type;
...
type lexer is tagged record
input : file_access;
index : integer;
end record;
...
my_lexer : lexer;
input_file_name : bounded_string;
input_file : aliased ada.text_io.file_type;
next_token : token;
begin
input_file_name := get_input_file;
ada.text_io.open(
file => input_file,
mode => ada.text_io.in_file,
name => to_string(input_file_name)
);
my_lexer := (input => input_file'access, index => 0);
next_token := my_lexer.get_next_token;
ada.text_io.put_line(to_string(next_token.text));
end main;
Seems a bit convoluted, but I believe it's a way to ensure an access of input_file lives for as long as the procedure.

Gnat (ada95) comfused by error "... is not visible" using my package

Using Gnat 7.4.0.
I'm an Ada noob, and confused by the error message:
$ gnat make list_test.adb
x86_64-linux-gnu-gcc-7 -c list_test.adb
list_test.adb:9:18: "List" is not visible (more references follow)
list_test.adb:9:18: non-visible declaration at linked_list.ads:19
x86_64-linux-gnu-gnatmake-7: "list_test.adb" compilation error
I don't understand why my List is not visible.
I'm trying to write a linked list with a generic payload to teach myself Ada. I wrote a binary tree with a similar layout (except the generic) which did not exhibit this error.
The .ads:
with Ada.Unchecked_Deallocation;
generic
type Payload_Type is private;
package Linked_List is
type List_Node;
type List_Node_Pointer is access all List_Node;
type List_Node is
record
payload : Payload_Type;
next : List_Node_Pointer := null;
prev : List_Node_Pointer := null;
end record;
type List is
record
head : List_Node_Pointer := null;
tail : List_Node_Pointer := null;
count : Natural := 0;
end record;
type List_Pointer is access all List;
procedure pushTail( base : in List;
payload : in Payload_Type );
procedure pushHead( base : in List;
payload : in Payload_Type );
function popTail( base : List ) return Payload_Type;
function contains( base : List;
payload : Payload_Type ) return Boolean;
private
procedure free is new Ada.Unchecked_Deallocation( List_Node, List_Node_Pointer );
end Linked_List;
And the .adb: (for the sake of completeness)
with Ada.Assertions;
package body Linked_List is
procedure pushTail( base : in out List;
payload : in Payload_Type ) is
begin
if ( base.head = null ) then
-- list is empy
base.head := new List_Node;
base.tail := base.head;
base.head.payload := payload;
else
-- list is not empty, add to the tail
base.tail.next := new List_Node;
base.tail.next.prev := base.tail;
base.tail := base.tail.next;
base.tail.payload := payload;
end if;
base.count := base.count + 1;
end push;
... -- More definitions for pushHead(), popTail(), contains()
private
end Linked_List;
The list_test.adb is something like:
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Linked_List;
procedure List_Test is
my_list : List;
found : Boolean;
begin
List.pushTail( my_list, Ada.Strings.Unbounded.To_Unbounded_String( "First" ) );
List.pushTail( my_list, Ada.Strings.Unbounded.To_Unbounded_String( "2nd" ) );
... etc.
Originally I had the syntax:
my_list : Linked_List.List;
But Gnat didn't like it - I don't understand why that wasn't accepted either.
EDIT: I've also tried
procedure List_Test is
package List_String is new List( Ada.Strings.Unbounded.Unbounded_String );
my_list : List_String;
But I get the same "List" is not visible error.
EDIT2:
with Linked_List;
procedure List_Test is
package String_List is new List( Ada.Strings.Unbounded.Unbounded_String ); use String_List;
my_list : String_List;
Still generates the error:
list_test.adb:10:32: "List" is not visible
list_test.adb:10:32: non-visible declaration at linked_list.ads:10
list_test.adb:10:89: "String_List" is undefined (more references follow)
As the Linked_List package is generic, you cannot reference the type Linked_List.List directly. You can only reference the List type in the instantiated package. Hence, try insert use List_String; directly after the instantiation of the generic package Linked_List, or use List_String.List to reference the List type in the List_String package:
procedure List_Test is
package List_String is
new Linked_List (Ada.Strings.Unbounded.Unbounded_String);
use List_String;
my_list : List
or
procedure List_Test is
package List_String is
new Linked_List (Ada.Strings.Unbounded.Unbounded_String);
my_list : List_String.List

Ada - getting string from text file and store in array

Hi im just wondering how to put data in an array if i loop txt and store it in A_Composite Name.
procedure Main is
type An_Array is array (Natural range <>) of A_Composite;
type A_Composite is
record
Name : Unbounded_String;
end record;
File : Ada.Text_IO.File_Type;
Line_Count : Integer := 0;
begin
Ada.Text_IO.Open (File => File,
Mode => Ada.Text_IO.In_File,
Name => "highscore.txt");
while not Ada.Text_IO.End_Of_File (File) loop
declare
Line :String := Ada.Text_IO.Get_Line (File);
begin
--I want to store Line String to array. but i don't know how to do it
end;
end loop;
Ada.Text_IO.Close (File);
end Main;
Ok, you have an unconstrained array here. This has implications; you see an unconstrained array gains its definite length when the object (general sense, not OOP) is declared or initialized.
As an example, let's look at strings (which are unconstrained arrays of characters) for an example to see how this works:
-- Create a string of 10 asterisks; the initialization takes those bounds.
A : constant string(1..10):= (others => '*');
-- Create a string of 10 asterisks; but determined by the initialization value.
B : constant string := (1..10 => '*');
-- Another way of declaring a string of 10 asterisks.
C : constant string := ('*','*','*','*','*','*','*','*','*','*');
Now, you can get these bounds from a function call; this means that we can use function-calls to return these values recursively.
Function Get_Text return An_Array is
Package Unbounded renames Ada.Strings.Unbounded;
-- You'll actually want the Get_Line that takes a file.
Function Get_Line return Unbounded.Unbounded_String
renames Unbounded.Text_IO.Get_Line;
begin
return (1 => (Name => Get_Line)) & Get_Text;
exception
when End_Error => return ( 1..0 => (Name => Unbounded.Null_Unbounded_String) );
end Get_Text;
So, that's how you'd do it using an unconstrained array.

Implementing an abstract function with access types in Ada

I have a package called Statements with an abstract type called Statement and an abstract function called execute(). In another package I have a type CompoundStatement which is a type Statement and it implements the execute() function.
I have a function called createStatement(). It's purpose is to evaluate a token of type Unbounded_String and determine what keyword it contains. Then based on this keyword it will generate an access type based on this keyword.
So far so good.
But what I can't figure out how to do is call the correct execute method. I only have one keyword coded in right now because it's not working yet.
Sorry if my description sounds convoluted.
package Statements is
type Statement is abstract tagged private;
type Statement_Access is access all Statement'Class;
function execute(skip: in Boolean; T: in TokenHandler; S: in Statement) return Integer is abstract;
private
type Statement is abstract tagged
record
tokens: Vector;
end record;
end Statements;
procedure createStatement(T : in TokenHandler; stmt: out Statement_Access) is
currenttoken : Unbounded_String;
C : CompoundStatement;
begin
currenttoken := To_Unbounded_String(TokenHandlers.getCurrentToken(T));
if currenttoken = "begin" then
createCompoundStatement(T, C);
stmt := new CompoundStatement;
stmt.all := Statement'Class(C);
end if;
end createStatement;
procedure createCompoundStatement(T : in TokenHandler; C: out CompoundStatement) is
begin
C.tokens := T.tokens;
end createCompoundStatement;
function execute(skip: in Boolean; T: in TokenHandler; C: in CompoundStatement) return Integer is
TK: TokenHandler := T;
stmt: Statement_Access;
tokensexecuted: Integer;
currenttoken : Unbounded_String;
begin
TokenHandlers.match("begin", TK);
currenttoken := To_Unbounded_String(TokenHandlers.getCurrentToken(TK));
while(currenttoken /= "end") loop
Put(To_String(currenttoken));
createStatement(T, stmt);
tokensexecuted := execute(skip, TK, stmt); //ERROR OCCURS HERE
TokenHandlers.moveAhead(tokensexecuted, TK);
currenttoken := To_Unbounded_String(TokenHandlers.getCurrentToken(TK));
end loop;
TokenHandlers.match("end", TK);
return TokenHandlers.resetTokens(TK);
end execute;
I get this error when I compile:
statements-statementhandlers.adb:35:28: no candidate interpretations match the actuals:
statements-statementhandlers.adb:35:46: expected type "CompoundStatement" defined at statements-statementhandlers.ads:14
statements-statementhandlers.adb:35:46: found type "Statement_Access" defined at statements.ads:6
statements-statementhandlers.adb:35:46: ==> in call to "execute" at statements-statementhandlers.ads:10
statements-statementhandlers.adb:35:46: ==> in call to "execute" at statements.ads:8
The third parameter to execute is expected to be a (child of) Statement, but what you’ve given it is a pointer to a (child of) Statement. You probably want
tokensexecuted := execute(skip, TK, stmt.all);
As a matter of style, by the way, it’s usually best to make the dispatching parameter the first; you could then (in Ada 2005) say
tokensexecuted := stmt.execute(skip, TK);

Resources