views:

290

answers:

5

I want to output a header line in a log file and then a line of '-' before the data. To do this I create a string of the header and then outpout the same number of '-'.

But the below code always fails with a CONSTRAINT_ERROR because the generated string is not 1024 characters. In Ada string assignments require exactly the same length not just sufficient capacity.

Option 1) is to compute the exact length but that is brittle to future changes. Option 2) is to use something other than String.

procedure F() is 
    Msg : String(1..1024);
begin
    Open_Log();
    Msg :=       FLS(" Field1", 12) &
           "|" & FLS(" Field2", 12) &
           "|" & FLS(" Field3", 16);

    Log_To_File("# " & Msg);
    Log_To_File("# " & Fill_String(Msg'Last, '-'));
end;
+1  A: 

One approach might be to write a function that fills a fixed length string with a dynamically sized input string, padding with spaces:

procedure Pad_String(Str: in String; Dest: out String; Len: out Integer) is
begin
    Len := Str'Last - Str'First + 1;
    Dest(Dest'First .. Dest'First + Len - 1) := Str(Str'First .. Str'First + Len - 1);
    Dest(Dest'First + Len .. Dest'Last) := Fill_String(Dest'Last - Len, ' ');
end Pad_String;

Ada's string handling allows you to pass any fixed length buffer into Dest and the 'First and 'Last attributes will be correct within the body of the procedure.

Then, your code could become:

procedure F() is     
    Msg : String(1..1024);    
    Len : Integer;
begin    
    Open_Log();    
    Pad_String(      FLS(" Field1", 12) &    
               "|" & FLS(" Field2", 12) &    
               "|" & FLS(" Field3", 16),
               Msg,
               Len);

    Log_To_File("# " & Msg(1 .. Len));    
    Log_To_File("# " & Fill_String(Len, '-'));    
end;    
Greg Hewgill
Weird. If you are keeping track of the lengths and slicing, why bother to "pad" outside of the slice?
T.E.D.
@T.E.D.: The `Pad_String` function is more useful in a general context if you always initialise the remainder of the `Dest` string. Also it's good practice to not leave any uninitialised data around; Ada programmers tend to be very conservative.
Greg Hewgill
A: 

I worked out how to use Unbounded_String. That type will accept other sized strings.

You can't build an unbounded string with the & operator unless you are using unbounded strings so use the To_Unbounded_String function.

with Ada.Strings.Unbounded;
procedure F() is  
   use Ada.Strings.Unbounded;
   Msg : Unbounded_String;
begin 
   Open_Log(); 
   Msg := Ada.Strings.Unbounded.To_Unbounded_String(
                FLS(" Field1", 12) & 
          "|" & FLS(" Field2", 12) & 
          "|" & FLS(" Field3", 16)); 

   Log_To_File("# " & Ada.Strings.Unbounded.To_String(Msg)); 
   Log_To_File("# " &
          Fill_String(Ada.Strings.Unbounded.Length(Msg), '-')); 
end; 
mat_geek
That'll work, but it is unnessecary. You can do it just fine, and much simpler, with plain old Ada Strings.
T.E.D.
+2  A: 

As a convenience, you can use the String constructor functions in Ada.Strings.Fixed, Ada.Strings.Bounded or Ada.Strings.Unbounded. These overload the * operator to "replicate a character or string a specified number of times." For example,

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
   ...
   Log_To_File("# " & Length(Msg) * '-');
trashgod
True, but you can accomplish the exact same thing with a simple string declaration and the `others => '-'` initialization
T.E.D.
Merely a convenience of one's chosen string model.
trashgod
+2  A: 

Just declare Msg as a String instead of a String(1 .. 1024)

procedure F() is 

    Msg: String  
    :=       FLS(" Field1", 12) &
       "|" & FLS(" Field2", 12) &
       "|" & FLS(" Field3", 16);
    --// this 'magically' declares Msg as a String(1 .. Something)
    --// with the right Something

begin
   Open_Log();

   Log_To_File("# " & Msg);
   Log_To_File("# " & Fill_String(Msg'Last, '-')); --'
end;
Ahhh yes! The correct answer from a user with a rep of 1. You just made my day, stefan.
T.E.D.
I liked it enough that I reformatted it a bit so make SO's code viewer gadget display it nicer. If you don't like it, feel free to revert it.
T.E.D.
In one other place in my code I need to build a string inside a loop. This will work in a function decleration but how do I do it inside a loop?
mat_geek
Why not put that one up as another SO question?I'd have to see the actual requirements, but the best options are probably to either put the code in a function (returning the string), perhaps with some recursion thrown in, or to use Ada.Strings.Unbounded instead. If you really, really have to dynamically build a string in stages, you should probably be using Ada.Strings.Unbounded. There is almost always a way around it though.
T.E.D.
+2  A: 

A lot of folks who are used to the C way of building strings in steps have trouble wrapping their minds around Ada strings, which you are supposed to initialize and use as-is. When you grok this fact about Ada strings, the solution becomes much simpler. I can even throw out your "Fill" routine.

procedure F() is  
   Msg : constant String
      := FLS(" Field1", 12) & 
       "|" & FLS(" Field2", 12) & 
       "|" & FLS(" Field3", 16); 
   Separator : constant String := (1..Msg'length => '-'); --'
begin 
   Open_Log(); 

   Log_To_File("# " & Msg); 
   Log_To_File("# " & Separator); 
end;

(Note: The comment is a hack to get SO's colorizer back on track)

If you didn't have to have the separator the same length, you wouldn't even need to declare the variable.

If it were me, I'd do something like have Log_To_File keep track of lengths and generate its own properly-sized separator upon request. Then you could just write:

Open_Log();
Log_To_File ("# " & FLS(" Field1", 12) & 
       "|" & FLS(" Field2", 12) & 
       "|" & FLS(" Field3", 16)); 
Log_Separator_To_File;
T.E.D.
+1 for advocating an alternate mindset; nice colorizer hack, too!
trashgod