Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to format text output in Ada

Is there a way to format the string being outputted? I'm trying get a pretty view of the following output

1: Ashley | 01033438392 | Wellington, New Zealand | 1987- 4-14  
2: Aloha | 01087651234 | Hawaii, United States of America | 1988- 9-23
3: Jack | 01082840184 | Beijing, China | 1989- 6-19

If I was programming in C, I would do something like

printf("%10s | %11s | %20s | %4d-%2d-%2d\n",name,phone,address,year,month,day);

Would it be possible to do this kind of formatting in Ada 05?

PS Please just ignore the names, phone numbers, address, and birthdate. I made them up in like 30 seconds...

like image 334
Chang Hyun Park Avatar asked May 17 '11 14:05

Chang Hyun Park


3 Answers

It can be done, but the mechanisms are a bit cumbersome and quite a bit more verbose.
What I would generally do is write separate procedures to handle your more complicated output, e.g. dates and use that with the rest of the string handling for clarity.

package Integer_IO is new Ada.Text_IO.Integer_IO (Integer);

procedure Output_Date ( Day : in Integer; Month: in Integer; Year: in Integer) is 
begin  
  Integer_IO.Put(Item => Day, Width => 2); 
  Text_IO.Put("-");
  Integer_IO.Put(Item => Month, Width => 2); 
  Text_IO.Put("-");
  Integer_IO.Put(Item => Year, Width => 4);
end Output_Date;

procedure Output_String ( Item : in String; 
                          Width : in Integer; 
                          Separator : in String := "|";
                          Truncate : Boolean := False) is 
  Field_Index : Integer := Text_IO.Col;
begin 
  if Item'length > Width and Truncate then 
    Text_IO.Put(Item(1..Width) & Separator);
  else 
    Text_IO.Put(Item) & Separator;
  end if;

  Text_IO.Set_Col ( Field_Index + Width + 1 );
end Output_String;

This will enforce fixed length fields, which will optionally allow truncation of long strings, or else move subsequent entries onto the next line. Set_Col will set the line position for the next write, potentially placing it on the next line if the current write position has already exceeded the one requested.

I threw string truncation in there as a chance to use array slicing and Text_IO's output manipulation, but I'm not generally a fan of default truncation, as allowing the string to overrun the requested width or indenting on the next line tend make formatting errors more obvious.

So printing out something like your first line, given the code above, might look something like:

Name  : String  := "Ashley"
Phone : String  := "01033438392"
Address: String := "Wellington, New Zealand"

Day    : Integer := 14;
Month : Integer  := 4;
Year   : Integer := 1987;

Output_String(Item=> Name,    Width => 10);
Output_String(Item=> Phone,   Width => 11);
Output_String(Item=> Address, Width => 20);
Output_Date(Day,Month,Year);

Text IO in Ada is usually cumbersome, but generally has the virtue of making what you are doing relatively clear.

like image 92
Greg Avatar answered Oct 20 '22 12:10

Greg


Note that in C++ these days printf() is on the verge of being depreciated, in favor of using streams with stream formatters. It is convenient, but massively unsafe (in at least a couple senses of the word). These days developers are encouraged to use C++ streams (with their assorted manipulators) instead.

In Ada you can manipulate strings in a very similar style to C++ streams using the string catenation operator & where C++ folks use the stream insertion operator (<<). In some ways, Ada's method is better because you can nest catenated expressions, which you can't do with stream-inserted expressions.

The problem here is that there aren't any handy equivalents to the C++ formatters like setfill(), hex, and setw(). There really ought to be, and (hex excepted) they aren't tough to write yourself, but for now they don't exist.

For example, a setw()/setfill() equivalent would be something like:

Fill_Char : Character := ' ';

function Set_Fill (New_Fill : Character) return String is
begin
    Fill_Char := New_Fill;
    return "";
end Set_Fill;

--// Dumb tail-recursive implementation. 
function Set_Width(Source : in String; Width : in Positive) return String is
begin
    if Width <= Source'length then --'
        return Source;
    else 
        return Fill_Char & Set_Width(Source, Width - 1);
    end if;
end Set_Width;

Unfilled_String : constant String := "123456";
Filled_String : constant String := Set_Width(Unfilled_String & Set_Fill('0'), 8);
--// The above string should end up being "00123456"

If you really want a printf() interface, printf() is quite callable from Ada of course. You have to worry about transitioning between Ada's sized strings and C's nul-terminated strings, but that's what Ada.Interfaces.C.Strings is there for.

like image 31
T.E.D. Avatar answered Oct 20 '22 11:10

T.E.D.


There is auxiliary tools for this particular format setup.

Package Ada.Text_IO.Integer_IO

procedure Put(Item : in Num; Width : in Field := Default_Width; Base : in Number_Base := Default_Base);

Puts a field with Item aligned to right and white-space character as filler. Where Width is field width and Base is 10 as defualt.

Package Ada.Strings.Fixed

function Head (Source : in String; Count : in Natural; Pad : in Character := Space) return String;
function Tail (Source : in String; Count : in Natural; Pad : in Character := Space) return String;

Returns a formatted string. Where Count is the field width and Pad is filler for the field. Head aligns string to left. Tail aligns string to right.

Let column width be 8 character long and use dash as filler.

Put_Line (Head ("Ashley", 8, '-'));
Put_Line (Head ("Aloha", 8, '-'));
Put_Line (Head ("Jack", 8, '-'));
Put_Line (Tail ("Ashley", 8, '-'));
Put_Line (Tail ("Aloha", 8, '-'));
Put_Line (Tail ("Jack", 8, '-'));

Output

Ashley--
Aloha---
Jack----
--Ashley
---Aloha
----Jack

Attribute discrete_type'Width

Returns a length which the discrete type requires to be represented as text.

Example

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
with Ada.Calendar; use Ada.Calendar;

procedure Test is

   subtype Index is Positive range 95 .. 1223;

   procedure Put_Line ( I : in out Index; Name : String; Phone : Natural; Address : String; T : in out Time ) is
   begin
      Put (I, Index'Width);
      Put (": ");
      Put (Head (Name, 10, ' '));
      Put (" | ");
      Put (Tail (Phone'Img (Phone'Img'First + 1 .. Phone'Img'Last), 13, '0'));
      Put (" | ");
      Put (Head (Address, 20, ' '));
      Put (Year (T), Year_Number'Width);
      Put ("-");
      Put (Month (T), Month_Number'Width);
      Put ("-");
      Put (Day (T), Day_Number'Width);
      I := Positive'Succ (I);
      T := T + Duration (60 * 60 * 24 * 3);
      New_Line;
   end;

   I : Index := Index'First;
   Now : Time := Clock;
begin

   Put_Line (I, "Ashley", 1033438392, "Wellington, New Zealand", Now);
   Put_Line (I, "Aloha", 01087651234, "Hawaii, United States of America", Now);
   Put_Line (I, "Jack", 01082840184, "Beijing, China", Now);
   I := Index'Last - 3;
   Put_Line (I,"Ashley", 1033438392, "Wellington, New Zealand", Now);
   Put_Line (I,"Aloha", 01087651234, "Hawaii, United States of America", Now);
   Put_Line (I,"Jack", 01082840184, "Beijing, China", Now);

end;

Output

   95: Ashley     | 0001033438392 | Wellington, New Zeal 2015-  5- 24
   96: Aloha      | 0001087651234 | Hawaii, United State 2015-  5- 27
   97: Jack       | 0001082840184 | Beijing, China       2015-  5- 30
 1220: Ashley     | 0001033438392 | Wellington, New Zeal 2015-  6-  2
 1221: Aloha      | 0001087651234 | Hawaii, United State 2015-  6-  5
 1222: Jack       | 0001082840184 | Beijing, China       2015-  6-  8

I would recommend to create a type for a phone number, I dunno if it should be string or number with the about of heading zeros, phone number can have different length I guess.

like image 35
Jossi Avatar answered Oct 20 '22 10:10

Jossi