(**********************************************************************

    :Program.    SrcScanner.mod
    :Contents.   Source code scanner for Make [bne]
    :Author.     Nicolas Benezan [bne]
    :Address.    Postwiesenstr. 2, D7000 Stuttgart 60
    :Phone.      711/333679
    :Copyright.  Amok internal, NOT in the public domain !
    :Language.   Modula-2
    :Translator. M2Amiga A+L V3.2d
    :Imports.    ErrorReq1.2 [bne]
    :History.    V1.0b [bne] 6.Feb.1989
    :History.    V1.1a [bne] 7.Feb.1989 (some bugs fixed)
    :History.    V1.2a [bne] 11.Feb.1989 ($m- bug fixed, SYSTEM is ignored)
    :Update.     17.Feb.1989 comments added

**********************************************************************)

IMPLEMENTATION MODULE SrcScanner;

(* The Scanner assumes that the source code is syntactically correct *)
(* If it is not, some imports may be lost, but the programm continues normally *)

FROM Arts       IMPORT ModType;
FROM FileSystem IMPORT File,Response,Lookup,Close,ReadChar;
FROM Str        IMPORT Compare;
FROM SYSTEM     IMPORT ADR;
IMPORT ASCII;

CONST   BufferSize=512;
        oldFile=FALSE;

VAR     SrcFile:File;
        SrcType:ModType;
        InsideImportList:BOOLEAN;  (* IMPORT ident { ","(*here*) ident } *)
        Char:CHAR;                 (* current char *)
        CommentLevel:INTEGER;      (* count nested comments *)
        Ident:ARRAY [0..40] OF CHAR;

PROCEDURE ReadCh;
BEGIN
  ReadChar(SrcFile,Char);
END ReadCh;

PROCEDURE SkipSemicolon;
BEGIN
  WHILE (Char#";") AND NOT SrcFile.eof DO
    ReadCh;
  END;
END SkipSemicolon;

PROCEDURE SkipSpaces;
BEGIN
  WHILE (Char=" ") AND NOT SrcFile.eof DO
    ReadCh;
  END;
END SkipSpaces;

PROCEDURE LetterOrDigit(C:CHAR):BOOLEAN;
BEGIN
  RETURN (C>="A") AND (C<="Z") OR
         (C>="a") AND (C<="z") OR
         (C>="0") AND (C<="9");
END LetterOrDigit;

(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
(* overread comment (with nested comments), detect (* $M- *)            *)
(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
PROCEDURE CheckComment;
BEGIN
  ReadCh;
  IF Char#"*" THEN
    RETURN;
  END;
  INC(CommentLevel);
  ReadCh;
  WHILE NOT SrcFile.eof DO
    IF Char="(" THEN
      CheckComment;    (* nested comment *)
    END;
    IF (Char="$") AND (CommentLevel=1) THEN
      ReadCh;
      IF CAP(Char)="M" THEN
        ReadCh;
        IF Char="-" THEN
          SrcType:=noImp;   (* (*$M-*) *)
        END;
      END;
    END;
    IF Char="*" THEN
      ReadCh;
      IF Char=")" THEN (* end of comment *)
        DEC(CommentLevel);
        RETURN;
      END;
    ELSE
      ReadCh;
    END;
  END; (* while *)
END CheckComment;

(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
(* read a "word" (read while alphanumeric)                              *)
(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
PROCEDURE ReadIdent(VAR Ident:ARRAY OF CHAR);
VAR   Count:INTEGER;
BEGIN
  Ident[0]:=Char; (* first char already read *)
  Count:=1;
  LOOP
    IF SrcFile.eof THEN
      EXIT
    END;
    ReadCh;
    IF LetterOrDigit(Char) THEN
      IF Count<=HIGH(Ident) THEN
        Ident[Count]:=Char;
        INC(Count);
      END;
    ELSE
      EXIT
    END;
  END; (* loop *)
  IF Count<=HIGH(Ident) THEN
    Ident[Count]:=ASCII.nul;
  END;
END ReadIdent;

PROCEDURE StartScan(FileName:ARRAY OF CHAR):BOOLEAN;

  PROCEDURE CheckMODULE():BOOLEAN;
  BEGIN
    ReadIdent(Ident);
    IF Compare(Ident,"MODULE")=0 THEN
      SkipSpaces;
      ReadIdent(Ident); (* Module name *)
      SkipSpaces;
      IF Char="{" THEN
        SrcType:=lib;   (* MODULE name {libname,version} *)
      END;
      SkipSemicolon;
      RETURN TRUE;
    ELSE
      RETURN FALSE;
    END;
  END CheckMODULE;

BEGIN
  Lookup(SrcFile,FileName,BufferSize,oldFile);
  InsideImportList:=FALSE;
  CommentLevel:=0; (* enable (*$M-*) at level 1 *)
  SrcType:=mod;
  IF SrcFile.res=done THEN
    LOOP
      ReadCh;
      IF Char="(" THEN
        CheckComment;
      ELSIF (Char="M") THEN
        IF CheckMODULE() THEN
          EXIT
        END;
      ELSIF SrcFile.eof THEN
        EXIT
      END;
    END;
  END;
  CommentLevel:=1; (* disable (*$M-*) after MODULE header *)
  RETURN SrcFile.res=done;
END StartScan;

PROCEDURE FindNextImport(VAR ModuleName:ARRAY OF CHAR):BOOLEAN;
VAR     Exit,Found:BOOLEAN;

(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
(* Import block ends if declaration or statement block begins           *)
(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
  PROCEDURE CheckEnd;
  BEGIN
    ReadIdent(Ident);
    Exit:=(Compare(Ident,"BEGIN")=0) OR
          (Compare(Ident,"VAR")=0) OR
          (Compare(Ident,"TYPE")=0) OR
          (Compare(Ident,"CONST")=0) OR
          (Compare(Ident,"PROCEDURE")=0); (* end of import *)
  END CheckEnd;

(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
(* test if there's a "FROM" symbol                                      *)
(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
  PROCEDURE CheckFROM;
  BEGIN
    ReadIdent(Ident);
    IF Compare(Ident,"FROM")=0 THEN
      SkipSpaces;
      IF LetterOrDigit(Char) THEN
        ReadIdent(ModuleName);
        Found:=TRUE;
      END;
      SkipSemicolon;
    END;
  END CheckFROM;

(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
(* ReadIdent() and the next comma or semicolon                          *)
(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
  PROCEDURE ReadImportListEntry;
  BEGIN
    ReadIdent(ModuleName);
    WHILE (Char#",")AND(Char#";")AND NOT SrcFile.eof DO
      ReadCh;
    END;
    InsideImportList:=Char=",";
    ReadCh;
  END ReadImportListEntry;

(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
(* test if there's an "IMPORT" symbol                                   *)
(*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*)
  PROCEDURE CheckIMPORT;
  BEGIN
    ReadIdent(Ident);
    IF Compare(Ident,"IMPORT")=0 THEN
      SkipSpaces;
      IF LetterOrDigit(Char) THEN
        ReadImportListEntry;
        Found:=SrcFile.res=done;
      END;
    END;
  END CheckIMPORT;

BEGIN
  REPEAT
    IF InsideImportList THEN
      SkipSpaces;
      IF LetterOrDigit(Char) THEN
        ReadImportListEntry;
        RETURN SrcFile.res=done;
      END;
    END;
    IF SrcFile.eof THEN
      RETURN FALSE
    END;
    Found:=FALSE;
    Exit:=FALSE;
    WHILE NOT (Exit OR Found) DO
      CASE Char OF
        |"F": CheckFROM;
        |"I": CheckIMPORT;
        |"(": CheckComment;
        |"B","V","T","C","P": CheckEnd; (* BEGIN, VAR, TYPE, CONST, PROCEDURE *)
      ELSE
        ReadCh;
        Exit:=SrcFile.eof;
      END;
    END;
  UNTIL (Compare(ModuleName,"SYSTEM")#0) OR NOT Found;
  RETURN Found;
END FindNextImport;

PROCEDURE EndScan(VAR ModuleType:ModType);
BEGIN
  ModuleType:=SrcType;
  Close(SrcFile);
END EndScan;

END SrcScanner.

