Program Statistics

Program ProgStats is the most challenging of our text manipulation demonstrations. We are interested in monitoring the language features used by programmers and need to count certain keywords such as function used in their programs. The main difficulty is that we must exclude keywords in either one-line comments (//) or multi-line comments between braces ({}). Comments may be nested. We handle a one-line comment by removing the comment from the end of the string. We looked at the scanner source code of the Free Pascal compiler and decided to use an integer (BraceCommentLevel) to keep track of the level of possible nesting of comments within braces.

We demonstrate function ansiContainsText, which suits our needs precisely. AnsiContainsText(MainString, SubString) returns True only if SubString is found in MainString. The function identifier contains the word 'text' so it is not case sensitive. We do not want to count keywords if they appear in the main string as part of a variable name e.g var Force1 : real; contains the letters of the keyword for. Our solution is to search for keywords between spaces e.g. ' for ' and to add spaces before the start and after the end of each line of code. We have tested the code in a limited way and would appreciate any feedback on deficiencies revealed by your tests. We would be delighted to receive your own Pascal solution to the problem.

The main procedure CountKeywords contains within it functions Braces and Quote and procedure Initialise.

You need to edit the FILENAME constant so show the name of your text file containing a Pascal program.

program ProgStats;
  {$APPTYPE CONSOLE}
  //Does not handle comments in the (*Comment*) format.
uses
  SysUtils, strUtils;
const
  FILENAME = 'DelphiManager.txt';

procedure CountKeywords(strFilename : string);
var
  Keywords : array[1..10] of string;
  PasFile : textfile;
  CurrentChar : char;
  CurrentString, TempString : string;
  LineNumber, i, BraceCommentLevel, KeyWordId, LeftBracePosition,
             RightBracePosition, DoubleSlashPosition, QuotePosition : integer;
  FoundPositions : array[1..10] of integer;
  Quoted : Boolean;

function Braces : Boolean;
begin
  LeftBracePosition := pos('{', CurrentString);
  RightBracePosition := pos('}', CurrentString);
  if (LeftBracePosition = 0) and (RightBracePosition = 0) then
    result := False
  else
    result := True;
end;

function Quote : Boolean;
begin
  QuotePosition := pos(chr(39), CurrentString);
  if QuotePosition = 0 then
    result := False
  else
    result := True;
end;

procedure initialise;
var
  i : integer;
begin
  TempString := '';
  BraceCommentLevel := 0;
  LineNumber := 0;
  Keywords[1] := ' if ';
  Keywords[2] := ' then ';
  Keywords[3] := ' case ';
  Keywords[4] := ' for ';
  Keywords[5] := ' repeat ';
  Keywords[6] := ' while ';
  Keywords[7] := ' record ';
  Keywords[8] := ' procedure ';
  Keywords[9] := ' function ';
  Keywords[10] := ' do ';
  for i := 1 to 10 do
    begin
      FoundPositions[i] := 0;
    end;
  assignFile(PasFile, strFilename);
  reset(PasFile);
end;

begin
  initialise;
  while not eof(PasFile) do
    begin
      readln(PasFile, CurrentString);
      {Add spaces to start and end of line so that we can search
      for keywords between spaces such as ' for ' and hence exclude
      from the count variable names such as 'force' and 'information'}
      CurrentString := ' ' + CurrentString + ' ';
      inc(LineNumber);
      if Braces then
        begin
          for i := 1 to length(CurrentString) do
            begin
              CurrentChar := CurrentString[i];
              if CurrentChar = '{' then
                begin
                  inc(BraceCommentLevel);
                end;
              if CurrentChar = '}' then
                begin
                  dec(BraceCommentLevel);
                end;
              if BraceCommentLevel = 0 then
                begin
                  if CurrentChar in ['{','}'] then
                    TempString := TempString + ' '
                  else
                    TempString := TempString + CurrentChar;
                end;
            end; //for i
           CurrentString := TempString;
        end; //if braces
    //Check for one line // comment
    DoubleSlashPosition := pos('//', CurrentString);
    if DoubleSlashPosition > 0 then
      begin
        CurrentString := leftStr(CurrentString, DoubleSlashPosition - 1) + ' ';
      end;
    //Only search if not in brace comment
    if BraceCommentLevel = 0 then
      begin
        if Quote then
          begin
            Quoted := False;
            TempString := '';
            for i := 1 to length(CurrentString) do
              begin
                CurrentChar := CurrentString[i];
                if CurrentChar = chr(39) then
                  begin
                    Quoted := not Quoted;
                  end;
                if not Quoted then
                  begin
                    TempString := TempString + CurrentChar;
                  end;
              end; //for
           CurrentString := TempString;
         end; //if Quote
      for KeyWordId := 1 to 10 do
        begin
          if ansiContainsText(CurrentString, Keywords[KeyWordId]) then
            begin
              inc(Foundpositions[KeyWordId]);
            end;
          end;
        end;
    end;
  closeFile(PasFile);
  writeln(' Line count: ', LineNumber,#13#10);
  for KeyWordId := 1 to 10 do
    begin
      writeln(KeyWords[KeyWordId],'count: ', FoundPositions[KeyWordId]);
    end;
end;

begin
  CountKeywords(FILENAME);
  readln;
end.   

The program produced the following output from our test file.

Output from program ProgStats

Output from program ProgStats

Programming - a skill for life!

Introduction to the string manipulation of text files