-------- SIMTEL20 Ada Software Repository Prologue ------------
--
-- Unit name    : COUNT_OF_ADA_STATEMENTS_3
-- Version      : 1.2
-- Author       : Richard Conn
--              : TI Ada Technology Branch
--              : Box 801, MS 8007
--              : McKinney, TX  75069
-- DDN Address  : RCONN at SIMTEL20
-- Derivation   : COUNT_OF_ADA_STATEMENTS_2 by Richard Conn
-- Derivation   : COUNT_OF_ADA_STATEMENTS by Bill Whitaker
-- Date created : 4 Apr 85
-- Release date : 4 Apr 85
-- Last update  : 24 June 85
--
---------------------------------------------------------------
--
-- Keywords     :  Source analysis, Quantity, Statements
--
----------------:
--
-- Abstract     :
--  This procedure calculates the "STATEMENTS" of a valid Ada fragment
--  specified by a FILE_NAME string parameter.  It need not be a complete
--  compilation unit, but it should have closed all open parens and
--  strings.
--
--  The Ada statement is defined by a semicolon terminator
--  outside of comments, parentheses, or string or character literals.
--  This definition is insensitive to formatting or layout of the source.
--
--  There are exotic cases for which this will misestimate the count
--  but we have never encountered one in real code.
--
--  This procedure is derived from Bill Whitaker's original
--  COUNT_OF_ADA_STATEMENTS, and it does not change his original algorithm.
--  It adds a line count and a character-checksum hash (sum of POS values of
--  all non-space characters in the file mod 256).  It also adds a count
--  of the comment lines (over CAS2, which does not).
--
------------------ Revision history ---------------------------
--
-- DATE         VERSION         AUTHOR          HISTORY
-- 19850215     1.0             R Conn          Initial Release
-- 19850506     1.1             R Conn          Overflow Traps Added
-- 19850624     1.2             R Conn          Bug in Single-Quote Proc Fixed
--
------------------ Distribution and Copyright -----------------
--
-- This software is released to the Public Domain (note:
--   software released to the Public Domain is not subject
--   to copyright protection).
--
------------------ Disclaimer ---------------------------------
--
-- This software and its documentation are provided "AS IS" and
-- without any expressed or implied warranties whatsoever.
-- No warranties as to performance, merchantability, or fitness
-- for a particular purpose exist.
--
-- In no event shall any person or organization of people be
-- held responsible for any direct, indirect, consequential
-- or inconsequential damages or lost profits.
--
-------------------END-PROLOGUE--------------------------------

with Text_Io, Character_Set;
procedure Count_Of_Ada_Statements (File_Name : String;
                                   Statements : in out Natural;
                                   Line_Count : in out Natural;
                                   Comments : in out Natural;
                                   Hash : in out Natural) is
    --
    --  Returned values:
    --     STATEMENTS    Number of Ada code statements
    --     LINE_COUNT    Number of lines of text
    --     COMMENTS      Number of comments in the file
    --     HASH          Checksum (Mod 256 sum) of all non-space
    --                    (a space character as defined by character_set)
    --                    characters
    --
    Input : Text_Io.File_Type;
    Current_Line : String (1 .. 256); --  arbitrarily large
    Next_Char : Natural := 1;
    Last_Char : Natural := 0;
    C : Character;
    Level : Integer := 0;
    Ch_Pending : Boolean := False;
    Pending_Ch : Character;

    procedure Unget (Ch : Character) is
    begin
        Ch_Pending := True;
        Pending_Ch := Ch;
    end Unget;

    procedure Get (Ch : in out Character) is
    begin
        if Ch_Pending then
            Ch_Pending := False;
            Ch := Pending_Ch;
        else
            if Next_Char > Last_Char then
                loop
                    Text_Io.Get_Line (Input, Current_Line, Last_Char);

                    begin
                        Line_Count := Line_Count + 1;
                    exception
                        when others =>
                            null; -- trap overflow
                    end;

                    Next_Char := 1;
                    exit when Next_Char <= Last_Char;
                end loop;
            end if;

            Ch := Current_Line (Next_Char);

            if not Character_Set.Is_Space (Ch) then
                Hash := (Hash + Character'Pos (Ch)) mod 256;
            end if;

            Next_Char := Next_Char + 1;
        end if;
    end Get;

begin

    Text_Io.Open (Input, Text_Io.In_File, File_Name);
    Statements := 0;
    Line_Count := 0;
    Comments := 0;
    Hash := 0;

    loop
        Get (C);

        --  Check for comment on the line
        if C = '-' then
            Get (C);
            --  Which is signaled by the '-' following a '-'

            if C = '-' then
                --  Then just skip the rest of the line and go to the next
                Next_Char := Last_Char + 1;

                begin
                    Comments := Comments + 1;
                exception
                    when others =>
                        null; -- trap overflow
                end;
            end if;
        end if;

        --  Check for one of the characters which introduce code constructs
        --  like string or character literal or formal parameter list
        --  within which a ';' does not terminate a "line of code"
        if C = '(' or C = '"' or C = '%' or C = ''' then

            --  Check for opening parentheses
            --  Every ';' within is in a formal parameter list
            if C = '(' then
                --  Count the number of levels of parentheses
                Level := Level + 1;
                --  Read ahead until the whole construct is closed, LEVEL = 0

                while Level > 0 loop
                    Get (C);

                    if C = '(' then
                        --  Increase the level if another '(' is found
                        Level := Level + 1;
                    elsif C = ')' then
                        --  Decrease the level if a ')' is found
                        Level := Level - 1;
                    end if;
                end loop;

                --  Now check for string brackets of either kind, " or %
            elsif C = '"' or C = '%' then
                --  Treat them in parallel, one must lead off
                if C = '"' then
                    loop
                        Get (C);
                        --  Loop until the close comes
                        --  If there is a doubled character it starts again
                        exit when C = '"';
                    end loop;
                    --  The '%' is handled exactly the same way as '"'
                elsif C = '%' then
                    loop
                        Get (C);
                        exit when C = '%';
                    end loop;
                end if;

                --  Character literals are just three characters long
                --  including '
            elsif C = ''' then
                Get (C);
                Get (C);

                if C /= ''' then
                    Unget (C);
                end if;
            end if;

            --  Any ';' that can be found at this point after all
            --  exclusions must be a valid "line of code" terminator
        elsif C = ';' then
            begin
                Statements := Statements + 1;
            exception
                when others =>
                    null; -- trap overflow
            end;

        end if;

    end loop;

exception
    when Text_Io.End_Error =>
        Text_Io.Close (Input); --  close input file
    when Text_Io.Name_Error =>
        Text_Io.New_Line;
        Text_Io.Put ("Error in File Name ");
        Text_Io.Put (File_Name);
        Text_Io.New_Line;
        raise Text_Io.Name_Error;
    when others =>
        raise;
end Count_Of_Ada_Statements;
