Error fix: raised CONSTRAINT_ERROR : book1.adb:28 length check failed - ada

What I'm trying to do with Ada is to create a Book object and then print out that book. Problem is, the language is not very writable. When I compile, build the main file, and then execute the file, it gives me this error:
raised CONSTRAINT_ERROR : book1.adb:28 length check failed
Here's what I have in the body file:
package body Book1 is
function getAuthor (B: in Book) return AuthorName is
begin
return B.getAuthor;
end getAuthor;
function getTitle (B: in Book) return TitleName is
begin
return B.getTitle;
end getTitle;
function getNumberOfPages (B: in Book) return Pages is
begin
return B.getNumberOfPages;
end getNumberOfPages;
function createBook (Author: in String; Title: in String; NumberOfPages: in Float)
return Book is
B: Book;
begin
B.getAuthor := Author;
B.getTitle := Title;
B.getNumberOfPages := NumberOfPages;
return B;
end createBook;
end Book1;
Here's what I have in the spec file:
package Book1 is
type Book is tagged private;
subtype AuthorName is String (1 .. 300);
subtype TitleName is String (1 .. 750);
subtype Pages is Float range 0.0 .. 5000000.0;
function getAuthor(B: in Book) return AuthorName;
function getTitle(B: in Book) return TitleName;
function getNumberOfPages(B: in Book) return Pages;
function createBook(Author: in String; Title: in String; NumberOfPages: in Float)
return Book;
private
type Book is tagged
record
getAuthor: AuthorName;
getTitle: TitleName;
getNumberOfPages: Pages;
end record;
end Book1;
Here's what I have in the test file:
with Book1, Ada.Text_IO, Ada.Float_Text_IO;
use Book1, Ada.Text_IO, Ada.Float_Text_IO;
procedure testBook is
theRealBook: Book := createBook(Author => "Jordan White",
Title => "The Last Christmas",
NumberOfPages => 32.0);
procedure Print_Book (B: in Book'Class) is
begin
Put (getAuthor (B => B));
Put (getTitle (B => B));
Put (getNumberOfPages (B => B));
New_Line;
end Print_Book;
begin
Print_Book (theRealBook);
end testBook;
What exactly am I doing to get this error, and how can I fix it?

Your statement here:
theRealBook: Book := createBook(Author => "Jordan White",
Title => "The Last Christmas",
NumberOfPages => 32.0);
Has an implicit length of strings for both author and title (String Literals).
This means that when you assign author and title here:
B.getAuthor := Author;
B.getTitle := Title;
You are making the assumption that "Jordan white" is a string of length 300. (Same problem for Title)
I Suspect this is homework, so ill leave it up to you to figure out how to find the length, and bounds of strings, and how to assign array slices.

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.

Passing map to common procedure

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);

Why is my Ada program not outputting correctly?

Let me start by saying this is the first ada program I have ever created. I have no idea how it works, and my assignment is incredibly simple. However, the output is not working correctly. It works with the first variable, but not with the next two. It also prints the first variable weird. Here is my code:
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
Author, Title, Pages: String := " ";
begin
Put("Enter Author: ");
Get(Author);
Put("Enter Title: ");
Get(Title);
Put("Enter number of pages: ");
Get(Pages);
Put("Author: ");
Put(Author);
New_Line;
Put("Title: ");
Put(Title);
New_Line;
Put("Number of pages: ");
Put(Pages);
end Main;
The goal is simply to enter information about a book and the program reads it out to you. This is the output:
Enter Author: john
Enter Title: Enter number of pages: Author: j
Title: o
Number of pages: h
Side note, I couldn't get page numbers to work as an integer. The get and put methods just gave errors. That isn't important but if anyone can help make that an integer I would appreciate it.
Here's a possible solution:
with Ada.Text_IO;
procedure Text_Input is
type Page_Count is range 1 .. 10_000;
package Page_Count_Text_IO is new Ada.Text_IO.Integer_IO (Page_Count);
function Get_Line (Message : in String) return String;
function Get_Line (Message : in String) return Page_Count;
function Get_Line (Message : in String) return String is
begin
Ada.Text_IO.Put (Message);
return Ada.Text_IO.Get_Line;
end Get_Line;
function Get_Line (Message : in String) return Page_Count is
begin
return Result : Page_Count do
Ada.Text_IO.Put (Message);
Page_Count_Text_IO.Get (Result);
if Ada.Text_IO.Get_Line /= "" then
raise Constraint_Error
with "Page count followed by extra characters.";
end if;
end return;
end Get_Line;
Author : constant String := Get_Line ("Enter author: ");
Title : constant String := Get_Line ("Enter title: ");
Pages : constant Page_Count := Get_Line ("Enter number of pages: ");
begin
Ada.Text_IO.Put_Line ("Author: " & Author);
Ada.Text_IO.Put_Line ("Title: " & Title);
Ada.Text_IO.Put_Line ("Number of pages:" & Page_Count'Image (Pages));
end Text_Input;
Notice that I've made the Get_Line function for Page_Count check that you don't have any trailing garbage on the line, where you enter the number of pages.
I hope you don't disagree with my estimate that John will never write a single book of more than 10'000 pages. :-)

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.

Resources