Counting new lines in Ada - performance

I'm trying to write an efficient program to count the number of new line characters from standard input. I wrote the following program:
with Ada.Text_IO;
procedure Main is
New_Lines : Integer := 0;
begin
while not Ada.Text_IO.End_Of_File loop
declare
Line : String := Ada.Text_IO.Get_Line;
begin
New_Lines := New_Lines + 1;
end;
end loop;
Ada.Text_IO.Put_Line (Integer'Image(New_Lines));
end Main;
How can this be made more efficient? I'm noticing that the compiler warns about not using Line. Maybe there's a way of specifying that I'm just interested in skipping to the new line character?

You could use Ada.Text_IO.Skip_Line instead, to avoid storing the lines on the stack, and getting rid of the warning about Line you mention.
A mofified version of your program:
with Ada.Text_IO;
procedure Main is
New_Lines : Integer := 0;
begin
while not Ada.Text_IO.End_Of_File loop
Ada.Text_IO.Skip_Line;
New_Lines := New_Lines + 1;
end loop;
Ada.Text_IO.Put_Line (Integer'Image(New_Lines));
end Main;
This is not guaranteed to count the last line, though, unless the file terminator is directly preceeded by a line terminator. (Although it seems at least GNAT will count it)
Be aware that on some platforms, the line terminator is not just a new line character, on windows, for example, it is CR+LF.

Related

Problem with file reading, stuck without output when run

I'm trying to do a program that reads numbers from a file, outputs them into a vector and then writes them. The code compiles nicely, but when run, it gets stuck with just a prompt without delivering any output.
Program LectorDeEnteros;
type
Arreglo = array [1..30] of integer;
var
//Arch:text;
Prom:byte;
i:integer;
ArregloA:Arreglo;
Procedure CargadorVectorialdeArchivo (var ArregloA:Arreglo);
var
Arch:text;
i:integer;
Begin
assign (Arch,'Numeros.txt');
reset (Arch);
i := 1;
while not eof(Arch) do
Write(Arch);Read(ArregloA[i]);
i := i + 1;
End;
Begin
CargadorVectorialdeArchivo(ArregloA);
for i := 1 to 14 do
WriteLn(ArregloA[i]:3);
End.
As i said, there are no error messages, just the prompt and no output. I have to CTRL-Z to get it out of this "loop". The expected output would be the numbers of the array, one on each line.
Rewrite the procedure as this:
Procedure CargadorVectorialdeArchivo (var ArregloA:Arreglo);
var
Arch:text;
i:integer;
Begin
assign (Arch,'Numeros.txt');
reset (Arch);
i := 1;
while not eof(Arch) do
begin
Read(Arch,ArregloA[i]);
i := i + 1;
end;
End;
Putting Arch in front of the file tells the compiler that you want to read the contents from that file, not from the keyboard.

Ada - Skipping Whitespace using look_ahead

I have a procedure, that in theory, should be skipping whitespace using a look_ahead loop. Problem is, it's not working, if there's any whitespace in the input file, it is adding it to the array of records. I think my logic is correct, but could use another pair of eyes to let me know what I'm missing, and why it's not working.
PROCEDURE Read(Calc: OUT Calculation) IS
EOL: Boolean;
C: Character;
I: Integer := 1;
BEGIN
LOOP
LOOP
Look_Ahead(C, EOL);
EXIT WHEN EOL or C /= ' ';
Get(C);
END LOOP;
EXIT WHEN ADA.Text_IO.END_OF_FILE;
Look_Ahead(C, EOL);
IF Is_Digit(C) THEN
Calc.Element(I).Kind := Number;
Get(Calc.Element(I).Int_Value);
ELSE
Calc.Element(I).Kind := Symbol;
Get(Calc.Element(I).Char_Value);
END IF;
Calc.Len := Calc.Len+1;
IF Calc.Element(I).Char_Value = '=' THEN
EXIT;
END IF;
I := I+1;
END LOOP;
END Read;
EDIT: If any of the other procedures, the code for the record etc is needed for an answer, let me know and I will post it.
For Ada.Text_IO.Look_Ahead, ARM A.10.7(8) says
Sets End_Of_Line to True if at end of line, including if at end of page or at end of file; in each of these cases the value of Item is not specified. Otherwise, End_Of_Line is set to False and Item is set to the next character (without consuming it) from the file.
(my emphasis) and I think the "without consuming it" is key. Once Look_Ahead has found an interesting character, you need to call Get to retrieve that character.
I hacked this little demo together: I left end-of-file to exception handling, and I called Skip_Line once end-of-line’s been seen because just Get wasn’t right (sorry not to be more precise!).
with Ada.Text_IO;
with Ada.IO_Exceptions;
procedure Justiciar is
procedure Read is
Eol: Boolean;
C: Character;
begin
-- Instead of useful processing, echo the input to the output
-- replacing spaces with periods.
Outer:
loop
Inner:
loop
Ada.Text_IO.Look_Ahead (C, Eol);
exit Outer when Eol; -- C is undefined
exit Inner when C /= ' ';
Ada.Text_IO.Get (C); -- consume the space
Ada.Text_IO.Put ('.'); -- instead of the space for visibility
end loop Inner;
Ada.Text_IO.Get (C); -- consume the character which isnt a space
Ada.Text_IO.Put (C); -- print it (or other processing!)
end loop Outer;
Ada.Text_IO.Skip_Line; -- consume the newline
Ada.Text_IO.New_Line; -- clear for next call
end Read;
begin
loop
Ada.Text_IO.Put ("reading: ");
Read;
end loop;
exception
when Ada.IO_Exceptions.End_Error =>
null;
end Justiciar;
Usually it's better to read an entire line and parse it than to try to parse character by character. The latter is usually more complex, harder to understand, and more error prone. So I'd suggest something like
function De_Space (Source : String) return String is
Line : Unbounded_String := To_Unbounded_String (Source);
begin -- De_Space
Remove : for I in reverse 1 .. Length (Line) loop
if Element (Line, I) = ' ' then
Delete (Source => Line, From => I, Through => I);
end if;
end loop Remove;
return To_String (Line);
end De_Space;
Line : constant String := De_Space (Get_Line);
You can then loop over Line'range and parse it. Since I'm not clear if
Get(C);
Get(Calc.Element(I).Int_Value);
Get(Calc.Element(I).Char_Value);
represent 1, 2, or 3 different procedures, I can't really help with that part.

Best way to modify strings in VHDL

I'm currently writing a test bench for a VHDL design I made and I need to write a message to a text file. The message is of the format
[instance_name];[simulation_time]
(i.e. U0;700 ns) and the filename must be [instance_name].log. Getting the instance name and simulation time is no problem, but writing to a custom filename has been problematic. Under simulation, the instance name will be given in the format:
"U0\ComponentX\test\"
and I would like to replace the slashes with underscores. Is there an easy way to do this?
Our PoC Library has quite a big collection on string operations/functions. There is a str_replace function in PoC.strings that should solve your question. There is also the PoC.utils package with non string related functions, that could also be helpful in handling strings and file I/O.
A simple implementation:
function replace(str : STRING) return STRING
variable Result : STRING(str'range) := str;
begin
for i in str'range loop
if (Result(i) = '\') then
Result(i) := '_';
end if;
loop;
return Result;
end function;
Usage:
constant original : STRING := "U0\ComponentX\test\";
constant replaced : STRING := replace(original);
Simple replace character function that is a bit more versatile and does the same job would be (nothing wrong with #Paebbels's answer)
function fReplaceChar(
a : character;
x : character;
s : string) return string
is
variable ret : string(s'range) := s;
begin
for i in ret'range loop
if(ret(i) = a) then
ret(i) := x;
end if;
end loop;
return ret;
end function fReplaceChar;
If there are more than one character to replace, one can always stack the function:
function fReplaceChar(
a : character;
b : character;
x : character;
s : string) return string
is
begin
return fReplaceChar(b, x, fReplaceChar(a, x, s));
end function fReplaceChar;
or function call:
fReplaceChar(')','_',fReplaceChar(':','(','_',tb'instance_name));
So for example:
process
begin
report lf & tb'instance_name & lf &
fReplaceChar(')','_',fReplaceChar(':','(','_',tb'instance_name));
wait;
end process;
gives:
# ** Note:
# :tb(sim):
# _tb_sim__

Pascal - Writing Strange Characters

I am attempting to write a comment stripper in pascal. I run my code and pass it a C source code file and it strips the comments from the file and prints the result to terminal.
I am fairly new to pascal. I am getting some very strange output and I cannot figure out why. The code checks for comments line by line and prints characters one at a time. The comment stripper is printing what seems to be random characters whenever it reaches the start of a new line. I am using pascals Write(Str[i]) function to print characters and WriteLn() once the end of a line is reached.
I have no idea why im receiving weird output. I am running Linux Mint and can compile and run my code, but I receive this strange output. I also tried running my code on a Mac and received a run-time error:
Program Path: ./Assignment1
File Name: lol.c
Runtime error 2 at $00011532
$00011532
$0002F7F6
$000113FD
$00011328
$00000002
Here is my code
program Assignment1;
uses
Sysutils;
var UserFile : TextFile;
TString : String;
OLine : String;
i : integer;
isComment : boolean;
skip : boolean;
begin
{$I+}
WriteLn('Program Path: ', ParamStr(0));
WriteLn('File Name: ', ParamStr(1));
Assign(UserFile, ParamStr(1) + '.c');
Reset(UserFile);
isComment := false;
skip := true;
Repeat
Readln(UserFile, TString);
for i:= 0 to ((Length(TString) - 1)) do
begin
if(skip) then
begin
skip := false;
continue;
end;
if(isComment = false) Then
begin
if(TString[i] = '/') Then
begin
if(TString[i+1] = '/') Then
begin
break;
end
else if(TString[i+1] = '*') Then
begin
isComment := true;
skip := true;
continue;
end;
end;
Write(TString[i]);
if(i = Length(TString) - 1) Then
begin
Write(TString[i + 1]);
end;
end
else
begin
if(TString[i] = '*') Then
begin
if(TString[i + 1] = '/') Then
begin
isComment := false;
skip := true;
continue;
end;
end;
end;
end;
WriteLn();
Until Eof(UserFile);
end.
I receive random characters which range from standard keyboard symbols to unicode blocks such as the ones found here.
Does anyone have any suggestions?
As 500 - Internal Server Error says, Pascal strings are 1-based. Your references to slot zero are returning garbage. If these are 256-byte strings you're getting the length code, I don't recall the memory layout of the pointer-based strings to know what you're getting in that case. You're also losing the last character of every string because of this.
Beyond that I see a definite bug: Look at what happens with a line ending in /
I also do not understand this:
if(i = Length(TString) - 1) Then
begin
Write(TString[i + 1]);
end;
It seems to me it's writing an extra character but I'm not sure.

Reading text Files - single line vs. multiple lines

I am working on a particular scenario, where I have to read from a Text File, parse it, extract meaningful information from it, perform SQL queries with the information and then produce a reponse, output file.
I have about 3000 lines of code. Everything is working as expected. However I have been thinking of a connendrum that could possibly dissrupt my project.
The text file being read (lets call it Text.txt) may consist of a single line or multiple lines.
In my case, a 'line' is identified by its segment name - say ISA, BHT, HB, NM1, etc... each segment ending is identified by a special character '~'.
Now if the file consists of multiple lines (such that each line corresponds to a single segment); say:-
ISA....... ~
NM1....... ~
DMG....... ~
SE........ ~
and so on.... then my code essentially reads each 'line' (i.e. each segment), one at a time and stores it into a temp buffer using the following command :-
ReadLn(myFile,buffer);
and then performs evaluations based on each line. Produces the desired output. No problems.
However the issue is... what if the file consists of only a single line (consisting of multiple segments), represented as:-
ISA....... ~NM1....... ~DMG....... ~SE........ ~
then with my ReadLine command I read the entire line instead of each segment, one at a time. This doesn't work for my code.
I was thinking about creating an if, else statement pair...which is based on how many lines my Txt.txt file consists of..such as:-
if line = 1:-
then extract each segment at a time...seperated by the special character '~'
perform necessary tasks (3000 lines of code)
else if line > 1:-
then extract each line at a time (corresponding to each segment)
perform necessary tasks (3000 lines of code).
now the 3000 lines of code is repeated twice and I don't find it elegant to copy and paste all of that code twice.
I would appreciate if I could get some feedback on how to possibly solve this issue, such that, regardless of a one-line file or multiple-line file...when i proceed to evaluate, i only use one segment at a time.
There are many possible ways of doing this. Which is best for you might depend on how long these files are and how important performance is.
A simple solution is to just read characters one at a time until you hit your tilde delimiter.
The routine ReadOneItem below shows how this can be done.
procedure TForm1.Button1Click(Sender: TObject);
const
FileName = 'c:\kuiper\test2.txt';
var
MyFile : textfile;
Buffer : string;
// Read one item from text file MyFile.
// Load characters one at a time.
// Ignore CR and LF characters
// Stop reading at end-of-file, or when a '~' is read
function ReadOneItem : string;
var
C : char;
begin
Result := '';
// loop continues until break
while true do
begin
// are we at the end-of-file? If so we're done
if eof(MyFile) then
break;
// read in the next character
read ( MyFile, C );
// ignore CR and LF
if ( C = #13 ) or ( C = #10 ) then
{do nothing}
else
begin
// add the character to the end
Result := Result + C;
// if this is the delimiter then stop reading
if C = '~' then
break;
end;
end;
end;
begin
assignfile ( MyFile, FileName );
reset ( MyFile );
try
while not EOF(MyFile) do
begin
Buffer := ReadOneItem;
Memo1 . Lines . Add ( Buffer );
end;
finally
closefile ( MyFile );
end;
end;
I would use a file mapping via the Win32 API CreateFileMapping() and MapViewOfFile() functions, and then just parse the raw data as-is, scanning for ~ characters and ignoring any line breaks you might encounter in between each segment. For example:
var
hFile: THandle;
hMapping: THandle;
pView: Pointer;
FileSize, I: DWORD;
pSegmentStart, pSegmentEnd: PAnsiChar;
sSegment: AnsiString;
begin
hFile := CreateFile('Path\To\Text.txt', GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
if hFile = INVALID_HANDLE_VALUE then RaiseLastOSError;
try
FileSize := GetFileSize(hFile, nil);
if FileSize = INVALID_FILE_SIZE then RaiseLastOSError;
if FileSize > 0 then
begin
hMapping := CreateFileMapping(hFile, nil, PAGE_READONLY, 0, FileSize, nil);
if hMapping = 0 then RaiseLastOSError;
try
pView := MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, FileSize);
if pView = nil then RaiseLastOSError;
try
pSegmentStart := PAnsiChar(pView);
pSegmentEnd := pSegmentStart;
I := 0;
while I < FileSize do
begin
if pSegmentEnd^ = '~' then
begin
SetString(sSegment, pSegmentStart, Integer(pSegmentEnd-pSegmentStart));
// use sSegment as needed...
pSegmentStart := pSegmentEnd + 1;
Inc(I);
while (I < FileSize) and (pSegmentStart^ in [#13, #10]) do
begin
Inc(pSegmentStart);
Inc(I);
end;
pSegmentEnd := pSegmentStart;
end else
begin
Inc(pSegmentEnd);
Inc(I);
end;
end;
if pSegmentEnd > pSegmentStart then
begin
SetString(sSegment, pSegmentStart, Integer(pSegmentEnd-pSegmentStart));
// use sSegment as needed...
end;
finally
UnmapViewOfFile(pView);
end;
finally
CloseHandle(hMapping);
end;
end;
finally
CloseHandle(hFile);
end;

Resources