Dynamic or empty choice in aggregate must be the only choice

What is this ?

cards.adb:51:70: error: dynamic or empty choice in aggregate must be the only choice

for

Deck := (Deck.Number + 1, [for I in Deck.list'Range => Deck.list(I), Deck.List'Length+1 => Card]);

with definition:

type Card_list is array (Natural range <>) of Cards;
subtype Card_number_type is Natural range 0..Max_Card_Number;
function Default_list (Card_number: Card_number_type) return Card_List;
type Card_Deck (Number: Card_number_type := Max_card_number) is record
	List: Card_list (1..Number):= Default_list(Number);
end record;

This is an ackward syntax anyway, I wonder why either the extension aggegate isn’t allowed for array types ? It should be pretty logical and straightforward, the notion of adding an existing array to an element, given the new index.

There wasn’t enough code there to compile correctly to see what is going on.
Can you provide a fully compilable (minus the line in error) example so we can play with it? It doesn’t have to be expansive, just large enough to show the issue. Right now there are too many undefined types and fields to make full sense of it.

I don’t know what else to give.
Let us start with the following, that should be complete, but I can’t compile it:

pragma Ada_2022;
pragma Extensions_Allowed (On);

package Card_Package is
	type Card_Suite is (Clubs,Spades,Hearts,Diamonds);
	type Card_Value is (Two,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Ace,Jack,Queen,King);
	type Cards is record
		Suite: Card_Suite;
		Value: Card_Value;
	end record;
	Max_Card_Number : constant :=
		(Card_Suite'Pos(Card_Suite'Last)+1)
		*(Card_Value'Pos(Card_value'Last)+1);
	type Card_list is array (Natural range <>) of Cards;
	subtype Card_number_type is Natural range 0..Max_Card_Number;
	function Default_list (Card_number: Card_number_type) return Card_List;
	type Card_Deck (Number: Card_number_type := Max_card_number) is record
		List: Card_list (1..Number):= Default_list(Number);
	end record;
	procedure Shuffle (Deck: in out Card_Deck);
	procedure Deal (Card: out Cards; Deck: in out Card_Deck);
	procedure Replace (Card: in Cards; Deck: in out Card_Deck);
end Card_Package;

I just get “cannot generate code for file card_package.ads (package spec)
gnatmake: “card_package.ads” compilation error” which is the most uninformative error message ever.

The reason that the compiler can’t generate code for this spec is that there are subprogram bodies that it can’t see (Default_list, Shuffle etc). Some subprogram bodies can appear in a spec (e.g. an expression function, often in the private part), and Default_List might be one, but not the others.

What the compiler means is 'I can’t generate code for the spec, you need to compile the body`.

You’ll still get a lot of helpful syntax & semantic warnings/errors.

But I thought one could compile specifications ? I do not understand what principles rule here. I wrote the body, but I only get the “dynamic or empty choice in aggregate must be the only choice” message, so here we go again.
Try to compile that:

pragma Ada_2022;
pragma Extensions_Allowed (On);
with Ada.Numerics.Discrete_Random;

package body Card_Package is
	procedure Shuffle (Deck: in out Card_Deck) is
		subtype real_card_number is Card_Number_type range Deck.List'Range;
		package NR is new Ada.Numerics.Discrete_Random(real_card_number);
		use NR;
		Gen: Generator;
		Inter: Cards;	
	begin
		for I in Deck.List'Range loop
			Inter := Deck.list(I);
			Deck.list(I) := Deck.list(Random(Gen));
			Deck.list(Random(Gen)) := Inter;
		end loop;
	end Shuffle;
	procedure Deal (Card: out Cards; Deck: in out Card_Deck) is
	begin
		if Deck.Number < 1 then raise Constraint_Error; end if;
		Card := Deck.List(Deck.List'First);
		Deck := (Deck.Number - 1, Deck.list(2..Deck.List'Length));
	end Deal;
	procedure Replace (Card: in Cards; Deck: in out Card_Deck) is
	begin
		if Deck.Number = Card_number_type'Last then raise Constraint_Error; end if;
		Deck := (Deck.Number + 1, [for I in Deck.list'Range => Deck.list(I), Deck.List'Length+1 => Card]);
	end Replace;
	function Default_list (Card_number: Card_number_type) return Card_List is
		package RSP is new Ada.Numerics.Discrete_Random (Card_suite);
		package RVP is new Ada.Numerics.Discrete_Random (Card_value);
		use RSP, RVP;
		Value_Gen: RVP.Generator;
		Suite_Gen: RSP.Generator;
		type Array_boolean is array (Card_suite,Card_Value) of Boolean;
		Cards_done: Array_boolean := (others => (others => False));
		Random_Card: Cards;
		List: Card_List (1..Card_Number);
	begin
		Reset(Value_Gen); Reset(Suite_Gen);
		for I in 1..Card_Number loop
			loop
				Random_Card := (Random(Suite_Gen),Random(Value_Gen));
				exit when Cards_Done (Random_Card.Suite,Random_Card.Value);
				List(I) := Random_Card;
				Cards_Done (Random_Card.Suite, Random_Card.Value) := true;
			end loop;
		end loop;
		return List;
	end Default_list;
end Card_Package;

To build up an array (or a vector) incrementally, you might want to use concatenate (the “&” operator) and the slicing operation Arr(X .. Y) rather than an aggregate. You can also combine all of these (concatenation, slicing, and aggregates), but it looks like some of your array aggregates would work better as invocations of “&”.

1 Like

Oh, I didn’t know I could do that “Deck := (Deck.Number + 1, Deck.list & Card);” ! Thanks, this is much better. I still wondner why it would complain before.

The rule comes from the Ada RM: 4.3.3(17/5):

The discrete_choice_list of an array_component_association (including an iterated_component_association) is allowed to have a discrete_choice that is a nonstatic choice_expression or that is a subtype_indication or range that defines a nonstatic or null range, only if it is the single discrete_choice of its discrete_choice_list, and either there is only one array_component_association in the enclosing array_component_association_list or the enclosing aggregate is an array_delta_aggregate, not an array_aggregate.

The history behind the rule is that once you have a non-static range, the checking required to be sure that the array aggregate defines all of the components of the array without any overlap becomes significantly harder, requiring in some cases the creation of a set of run-time temporaries to support the check. It also becomes harder to read … :wink:

3 Likes

Mmmm… I thought so ! That’s why “&” doesn’t elicit complains. Elaboration of the new array subtype and its boundaries is automatic. Makes sense.
Also I have that line:

Deck := (Deck.List'Length - 1, Deck.list(2..Deck.List'Length));

That invariably raises the following error at runtime:

raised CONSTRAINT_ERROR : card_package.adb:23 discriminant check failed

Why can’t I change the discriminant in an aggregate ?

This is specific to GNAT and its source-based library approach. Most compilers happily compile specs, and even require that they be compiled.

You can check specs with GNAT with the -gnatc option:

gnatmake -gnatc card_package.ads

2 Likes

Also I have that line:

Deck := (Deck.List'Length - 1, Deck.list(2..Deck.List'Length));

That invariably raises the following error at runtime:

raised CONSTRAINT_ERROR : card_package.adb:23 discriminant check failed

Why can’t I change the discriminant in an aggregate ?

I think this might have been a side-effect of other problems with the code.

Default_List has the inner loop in the wrong place. It should be:

                for I in 1..Card_Number loop
                        loop
                                Random_Card := (Random(Suite_Gen),Random(Value_Gen));
                                exit when not Cards_Done (Random_Card.Suite,Random_Card.Value);
                        end loop;
                        List(I) := Random_Card;
                        Cards_Done (Random_Card.Suite, Random_Card.Value) := true;
                end loop;

Shuffle is calling Random twice per swap, when I believe you should only call it once:

                for I in Deck.List'Range loop
                        Inter := Deck.list(I);
                        Ran := Random(Gen);
                        Deck.list(I) := Deck.list(Ran);
                        Deck.list(Ran) := Inter;
                end loop;

You are using 'Length in a few places where 'Last would be more appropriate, though since most of your arrays generally are indexed starting with 1, 'Last and 'Length are the same, even though 'Last is more appropriate when indexing into the array.

Here is a version of your Card_Package that I got to work:

pragma Ada_2022;
pragma Extensions_Allowed (On);
with Ada.Numerics.Discrete_Random;

package body Card_Package is
	procedure Shuffle (Deck: in out Card_Deck) is
		subtype real_card_number is Card_Number_type range Deck.List'Range;
		package NR is new Ada.Numerics.Discrete_Random(real_card_number);
		use NR;
		Gen: Generator;
		Inter: Cards;	
                Ran : Real_Card_Number;
	begin
		for I in Deck.List'Range loop
			Inter := Deck.list(I);
                        Ran := Random(Gen);
			Deck.list(I) := Deck.list(Ran);
			Deck.list(Ran) := Inter;
		end loop;
	end Shuffle;
	procedure Deal (Card: out Cards; Deck: in out Card_Deck) is
	begin
		if Deck.Number < 1 then raise Constraint_Error; end if;
		Card := Deck.List(Deck.List'First);
		Deck := (Deck.Number - 1, Deck.list(2..Deck.List'Last));
	end Deal;
	procedure Replace (Card: in Cards; Deck: in out Card_Deck) is
	begin
		if Deck.Number = Card_number_type'Last then raise Constraint_Error; end if;
		Deck := (Deck.Number + 1, Deck.List & Card);
	end Replace;
	function Default_list (Card_number: Card_number_type) return Card_List is
		package RSP is new Ada.Numerics.Discrete_Random (Card_suite);
		package RVP is new Ada.Numerics.Discrete_Random (Card_value);
		use RSP, RVP;
		Value_Gen: RVP.Generator;
		Suite_Gen: RSP.Generator;
		type Array_boolean is array (Card_suite,Card_Value) of Boolean;
		Cards_done: Array_boolean := (others => (others => False));
		Random_Card: Cards;
		List: Card_List (1..Card_Number);
	begin
		Reset(Value_Gen); Reset(Suite_Gen);
		for I in 1..Card_Number loop
			loop
				Random_Card := (Random(Suite_Gen),Random(Value_Gen));
				exit when not Cards_Done (Random_Card.Suite,Random_Card.Value);
            end loop;
            List(I) := Random_Card;
            Cards_Done (Random_Card.Suite, Random_Card.Value) := true;
		end loop;
		return List;
	end Default_list;
end Card_Package;

With this as my test program:

with Ada.Text_IO; use Ada.Text_IO;
with Card_Package; use Card_Package;
procedure Test_Card is
   D : Card_Deck;
begin
   Put_Line ("Shuffling");
   Shuffle (D);
   Put_Line ("Dealing");
   New_Line;
   for I in 1 .. Max_Card_Number loop
      declare
         C : Cards;
      begin
         Deal (C, D);
         Put_Line (C.Value'Image & " of " & C.Suite'Image);
         if I mod 13 = 0 then
            New_Line;
         end if;
      end;
   end loop;
end Test_Card;
2 Likes

See here, the “discrimant check failed” error still pops up at run time. Your own test program, if the card deck is given a discriminant (say, 10), fails. Is there a rule forbidding to change the discrimant of a mutant record if it is given a default value, even with an aggregate ?

If you declare the object with a discriminant value for the type you cannot change it later. You want to declare it with no discriminant and inititialize it. Try:
D : Card_Deck := (Number => 10, others => <>);
or Ada 95:
D : Card_Deck := (Number => 10, List => Default_list(10));

NOTE: that is without seeing what line your error occurs on. So a bit of a lob in the dark.

EDIT: Tested in jdoodle online Ada compiler and works:

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Numerics.Discrete_Random;
 
procedure jdoodle is
   package Card_Package is
   	CC_error: exception;
   	type Card_Suite is (Clubs,Spades,Hearts,Diamonds);
   	type Card_Value is (Two,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Ace,Jack,Queen,King);
   	type Cards is record
   		Suite: Card_Suite;
   		Value: Card_Value;
   	end record;
   	Max_Card_Number : constant :=
   		(Card_Suite'Pos(Card_Suite'Last)+1)
   		*(Card_Value'Pos(Card_value'Last)+1);
   	type Card_list is array (Natural range <>) of Cards;
 
   	subtype Card_number_type is Natural range 0..Max_Card_Number;
 
   	function Default_list (Card_number: Card_number_type) return Card_List;
   	type Card_Deck (Number: Card_number_type := Max_card_number) is record
   		List: Card_list (1..Number):= Default_list(Number);
   	end record;
   	procedure Deal (Card: out Cards; Deck: in out Card_Deck);
   end Card_Package;
 
   package body Card_Package is
   	procedure Deal (Card: out Cards; Deck: in out Card_Deck) is
   	begin
   		if Deck.Number < 1 then raise Constraint_Error; end if;
   		Card := Deck.List(Deck.List'First);
   		Deck := (Deck.Number - 1, Deck.list(2..Deck.List'Last));
   	end Deal;
   	function Default_list (Card_number: Card_number_type) return Card_List is
   		package RSP is new Ada.Numerics.Discrete_Random (Card_suite);
   		package RVP is new Ada.Numerics.Discrete_Random (Card_value);
   		use RSP, RVP;
   		Value_Gen: RVP.Generator;
   		Suite_Gen: RSP.Generator;
   		type Array_boolean is array (Card_suite,Card_Value) of Boolean;
   		Cards_done: Array_boolean := (others => (others => False));
   		Random_Card: Cards;
   		List: Card_List (1..Card_Number);
   	begin
   		Reset(Value_Gen); Reset(Suite_Gen);
   		for I in 1..Card_Number loop
   			loop
   				Random_Card := (Random(Suite_Gen),Random(Value_Gen));
   				exit when not Cards_Done (Random_Card.Suite,Random_Card.Value);
               end loop;
               List(I) := Random_Card;
               Cards_Done (Random_Card.Suite, Random_Card.Value) := true;
   		end loop;
   		return List;
   	end Default_list;
   end Card_Package;
   use Card_Package;
   D : Card_Deck := (Number => 10, List => Default_list(10));
   C : Cards;
begin
   Put_Line(Max_Card_Number'Image);
   Put_line (D.Number'Image & ", " & D.List'Length'Image);
   Deal (C, D);
end jdoodle;
 52
 10,  10

gcc -c jdoodle.adb
gnatbind -x jdoodle.ali
gnatlink jdoodle.ali -o jdoodle
1 Like

Damn oh yes I forgot that rule ! Thank you.

As indicated by the earlier reply, if you specify a discriminant value when declaring an object, it is restricted to that value for its lifetime. In my test program, I didn’t specify the discriminant value when declaring the object of type Card_Deck, which has two distinct effects, first it uses the default value for the discriminant, whatever that may be, and it allows the discriminant value to be changed by a whole-object assignment.

4 Likes