How to use strings in Ada?

I’m learning Ada, and for the most part it’s making sense to me, but I’m having trouble with string handling. I’d like to translate the following 9 line Turbo Pascal program to Ada:

program stringdemo;
var st1, st2: string;
begin
st1 := 'Ada';
st2 := 'strings confuse me.';
writeln(st1);
st1 := st1 + ' ' + st2;
writeln(st1);
end.

Three questions:

  • How do I do this with unbounded strings?
  • How do I do this with bounded strings?
  • Are there any other ways to do this I should be aware of?

Thanks,

Steve

This is with unbounded strings:

with Ada.Strings.Unbounded;
with Ada.Text_IO.Unbounded_IO; use Ada.Text_IO.Unbounded_IO;

procedure String_Demo is
   use Ada.Strings.Unbounded;
   St1, St2 : Unbounded_String;
begin
   St1 := To_Unbounded_String ("Ada");
   St2 := To_Unbounded_String ("strings confuse me");
   Put_Line (St1);
   St1 := St1 & " " & St2;
   Put_Line (St1);
end String_Demo;
1 Like

… and this is with bounded strings - more work, as you can see.

with Ada.Strings.Bounded;
with Ada.Text_IO.Bounded_IO;

procedure String_Demo is
   package Bounded_Strings_256 is new Ada.Strings.Bounded.Generic_Bounded_Length
     (Max => 256);
   package Bounded_Strings_256_IO is new Ada.Text_IO.Bounded_IO
     (Bounded => Bounded_Strings_256);
   use Bounded_Strings_256;
   use Bounded_Strings_256_IO;
   St1, St2 : Bounded_String;
begin
   St1 := To_Bounded_String ("Ada");
   St2 := To_Bounded_String ("strings confuse me");
   Put_Line (St1);
   St1 := St1 & " " & St2;
   Put_Line (St1);
end String_Demo;
1 Like

Oh, ok.
This is actually easy, at least once you understand that Strings are just arrays, unconstrained arrays, but arrays nonetheless.

For illustration-purposes, we’re going to use non-string arrays to explain what’s going on.

Type Vector is Array (Positive range <>) of Natural;

-- Returns a vector from user-input.
Function Prompt return Vector is
  Empty : Constant Vector(2..1):= (others => 1);
  Function Collect( Result : in Vector := Empty ) return Vector is
  Begin
     Ada.Text_IO.Put_Line( "Enter a natural number (Negative to exit): " );
     Declare
        Input : String renames Ada.Text_IO.Get_Line;
        Value : Integer renames Natural'Val( Input );
     Begin
        Return Collect( Result & Value );
     End;
  Exception
     when Constraint_Error => Return Result;
  End Collect;
Begin
  Return Collect;
End Prompt;

Data_Set : Vector:= Prompt;

Now, Data_Set is unconstrained by its subtype and must be constrained by the initialization, which here is Prompt. Since Prompt returns an unconstrained array, it can be any length from 0 to its full index.

So, having constrained Data_Set with the initial value, while it is variable, the size is fixed, meaning you could have (1,4,7,5) and change it to (9,3,7,1), but not (7,7) or (2,3,3,1,8).

The other way to constrain a value would be like Data_Set_2 : Vector(2..7):= (others => 7);, which creates a length-5 vector and filling its elements with 7.

Now, we come to the syntactic sugar for Strings – Strings are merely arrays which have an element which is a character-type (meaning an enumeration having at least one element surrounded by single quotes) – the syntactic sugar allows you to say "Hello" instead of write ('H','e','l','l','o').

Three questions:

  • How do I do this with unbounded strings?
  • How do I do this with bounded strings?
  • Are there any other ways to do this I should be aware of?

I typically do something like:

Function "+"(Right : String) return Ada.Strings.Unbounded_Strings.Unbounded_String renames Ada.Strings.Unbounded_Strings.To_Unbounded_String;
Function "+"(Right : Ada.Strings.Unbounded_Strings.Unbounded_String) return String renames Ada.Strings.Unbounded_Strings.To_String;
--...
X : Unbounded_String:= +"Initial stuff."'
X:= X & (+"Other stuff");
--...
Return Result : String:= +X;

Which you can pretty much do with bounded as well.

1 Like

Thank you simonjwright. This worked! So did your other solution.

Your Pascal example can also be translated to fixed strings, although is more cumbersome, because you can only assign directly when you respect the size or use a slice. First, you will need to declare a string buffer with the necessary size (this is sometimes the difficult part).

This version is using Ada.Strings.Fixed.Head which keeps the direct assignments:

with Ada.Strings.Fixed;
with Ada.Text_IO; use Ada.Text_IO;

procedure String_Demo is
   use Ada.Strings;
   St1, St2 : String (1 .. 100);
begin
   St1 := Fixed.Head ("Ada", St1'Length);
   St2 := Fixed.Head ("strings confuse me", St2'Length);
   Put_Line (St1);
   St1 := Fixed.Head (Fixed.Trim (St1, Side => Right) & " " & St2, St1'Length);
   Put_Line (St1);
end String_Demo;

This version is using Ada.Strings.Fixed.Move, which is probably more efficient (less copying).

with Ada.Strings.Fixed;
with Ada.Text_IO; use Ada.Text_IO;

procedure String_Demo is
   use Ada.Strings;
   St1, St2 : String (1 .. 100);
begin
   Fixed.Move ("Ada", St1);
   Fixed.Move ("strings confuse me", St2);
   Put_Line (St1);
   Fixed.Move (Fixed.Trim (St1, Side => Right) & " " & St2, St1);
   Put_Line (St1);
end String_Demo;

This example is with assignment to slices, probably the most efficient version:

with Ada.Text_IO; use Ada.Text_IO;

procedure String_Demo is
    -- In this case, you have to initialize the buffer with the padding character.
   St1, St2 : String (1 .. 100) := (others => ' ');
   Ada_Str : constant String := "Ada";
   Confusion_Str : constant String := "strings confuse me";
begin
   St1 (1 .. Ada_Str'Last) := Ada_Str;
   St2 (1 .. Confusion_Str'Last) := Confusion_Str;
   Put_Line (St1);
   St1 (1 .. Ada_Str'Last + Confusion_Str'Last + 1) :=
     Ada_Str & " " & Confusion_Str;
   Put_Line (St1);
end String_Demo;

But you don’t usually do these variable-length assignments with fixed strings, but you declare them with the size you need and then return them as function results.

So a more realistic equivalent of your demo is simply:

with Ada.Text_IO; use Ada.Text_IO;

procedure String_Demo is
   Ada_Str : constant String := "Ada";
   Confusion_Str : constant String := "strings confuse me";
   St1 : constant String := Ada_Str & " " & Confusion_Str;
begin
   Put_Line (Ada_Str);
   Put_Line (St1);
end String_Demo;
2 Likes

Others have answered your question, but I’d like to touch on why you’re confused. In many languages, including Turbo Pascal, strings are magic. You cannot define a type that behaves like Turbo Pascal’s String, AFAIK. Ada has its share of magic things, but strings are not one of them. As pointed out, type String is an unconstrained 1-D array type, no different from any other such type. You can even declare your own character and string types. Ada.Strings.[Un]Bounded are no different from any other ADTs.

Your confusion comes from thinking in terms of TP’s strings when you’re using Ada. Once you start thinking in Ada terms when using Ada, things will become much simpler.

Type String is suitable for almost everything you normally need to do with strings, except when you need to store differing-length ones in a definite composite type (such as a record). For that you’d usually use a holder, but the designers of Ada 95, in their finite wisdom, decided to treat strings specially and create special-purpose ADTs for them.

2 Likes

Take a good book instead of copying other language’s code. That’s a terrible way to learn.

Thanks evanescente-ondine. What book(s) do you recommend? Because I have bad eyesight and because I make transcription errors copying code from paper to editor, I’d prefer a PDF format for my book. If it’s a print book, I need at least 12 point text. Thanks!

Thanks OneWingedShark. Your example is a little too advanced for my current understanding (I’ve been studying Ada for about 4 days), but I’m going to come back to it again and again to learn the many techniques you use in your example.

Thanks JC001. I didn’t know about Abstract Data Types before your post, now I’ll learn more about them. From you and others, I now know clear as day that a string is simply an array of characters, and I can use ADTs to manipulate these arrays of characters.

Forget ADTs for now… you’ll read about them in due time. Take Software “Construction and Data Structures with Ada 95”, you can find it online well enough.
The code examples are here.
I hate physical books when it comes to programming too. You won’t even have to copy from the pdf, for the most part.
It’s accessible and to your level.

That’s not entirely true.
(Adding in your own "+" or "&" operators to the following…)

Package Pascal_String with Pure is
   Subtype Byte is Interfaces.Unsigned_8;
   Type String is private;
   -- All the manipulation functions + create.
   Function "+"( Input : Standard.String ) return String
      with Pre => Input'Length in Byte'Range;
   Function "+"(Input : String) return Standard.String;
Private

   Type String is record
      Length : Byte:= 0;
      Text   : Standard.String(1..255):= (others => ASCII.NUL);
   end record
   with Size => 256 * Byte'Size;
   -- Convert to/from Internal.String via unchecked conversion.

   Function "+"( Input : Standard.String ) return String is
   ( Length => Byte(Input'Length),
     Text   => Input & (Input'Length+1..256 => ASCII.NUL)
    );
   Function "+"(Input : String) return Standard.String is
   ( Input.Text(1..Input.Length) );
End_Pascal_String;
1 Like

By a magic type in a language, I mean a type that a user cannot declare in that language. Character is a magic type in Ada. String is a magic type in Turbo Pascal.

1 Like

Is it?
I mean, I’ve used character-types myself.
The Base_64 encoder here, for example.

1 Like

n Ada, you can handle strings using both unbounded and bounded strings. Here’s how to translate your Turbo Pascal code:

With Unbounded Strings (from Ada.Strings.Unbounded)

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

procedure StringDemo is
st1, st2 : Unbounded_String;
begin
st1 := To_Unbounded_String(“Ada”);
st2 := To_Unbounded_String(“strings confuse me.”);
Put_Line(To_String(st1));
st1 := st1 & " " & st2;
Put_Line(To_String(st1));
end StringDemo;

With Bounded Strings (from Ada.Strings.Bounded):

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Bounded; use Ada.Strings.Bounded;

procedure StringDemo is
subtype Bounded_String_Type is Bounded_String(100);
st1, st2 : Bounded_String_Type;
begin
st1 := To_Bounded_String(“Ada”);
st2 := To_Bounded_String(“strings confuse me.”);
Put_Line(To_String(st1));
st1 := st1 & " " & st2;
Put_Line(To_String(st1));
end StringDemo;

Other Methods: You can also use fixed-length strings in Ada, but unbounded and bounded strings are more flexible for general usage.

Avoid unbounded strings they are heap-allocated. Bounded strings are basically useless.
Most tasks in Ada can be accomplished with fixed strings, which have clean syntax, slices, returned on the stack. String slices are essential for string processing because they allow you to avoid copying in most cases.

with Ada.Text_IO; use Ada.Text_IO;
procedure Test is
   St1 : constant String := "Ada";
   St2 : constant String := "strings confuse me.";
begin
   Put_Line (St1);
   Put_Line (St1 & ' ' & St2);
end;
2 Likes

[dmitry-kazakov], what’s wrong with allocating off the heap? Is Ada like C where I need to remember to free it when it’s no longer needed or is about to go out of scope?

Try to create a type identical to Standard.Character.

1 Like

Yes, in Ada like in C if you allocate memory you must explicitly free it later. Unbounded_String do it in the background, so you need not to care.
Use Ada.Finalization if you want to design a type like Unbounded_String.

Heap is bad because it requires interlocking to allocate and deallocate.

Of course, in Ada you can have a specially designed memory pool to improve performance like mark-and-release pool. But Unbounded_String uses a normal pool because there is no way to predict allocation and deallocation in general case.

1 Like