IMPLEMENTATION MODULE YaflLint;

FROM List IMPORT List;
FROM YaflParser IMPORT Walker, NonTerminal;
IMPORT Ref;
FROM YaflClasses IMPORT ClassDeclaration;
FROM YaflImport IMPORT ImportClause;
FROM YaflDesignator IMPORT Desig;
FROM YaflIdentifiers IMPORT Ident, IdentList;
FROM YaflMethods IMPORT MethodDeclaration;
FROM YaflMetImplementation IMPORT MethodImplementation;
FROM YaflModules IMPORT CompilationUnit, DefinitionModule;
FROM YaflNTList IMPORT NTList;
FROM YaflPredefined IMPORT PredefItems, PredefMethod;                        
FROM YaflStatements IMPORT Statement;                        

  CLASS NonTerminalRef;
    VAR
      TheRef, TheSourceRef: NonTerminal;
      
    REDEFINE METHOD CREATE (Ref, SourceRef: NonTerminal);
      BEGIN
      ASSERT Ref <> VOID;
      ASSERT SourceRef <> VOID;
      BASE;
      TheRef := Ref;
      TheSourceRef := SourceRef;
      END CREATE;
      
    METHOD Ref: NonTerminal;
      BEGIN
      RESULT := TheRef;
      END Ref;
      
    METHOD SourceRef: NonTerminal;
      BEGIN
      RESULT := TheSourceRef;
      END SourceRef;
      
    METHOD ToString: ARRAY OF CHAR;
      BEGIN
      WHAT Ref OF
        IN CompilationUnit:
          RESULT := "module " + TAG.Id.Data;
          END;
        IN ClassDeclaration:
          RESULT := "class " + TAG.Id.Data;
          END;
        END;
      END ToString;
      
    METHOD Warning (Mess: ARRAY OF CHAR);
      BEGIN
      SourceRef.Warning (Mess);
      END Warning;
                            
  END NonTerminalRef;
---------------------------------------------                        
  CLASS RefList;
    INHERITS List(NonTerminalRef);
    
    METHOD Find(What: NonTerminal): INTEGER;
      BEGIN
      RESULT := -1;
      FOR i := 0 TO Size-1 WHILE RESULT < 0 DO
        IF Get(i).Ref = What THEN
          RESULT := i;
          END;
        END;
      END Find;
      
    METHOD Includes (What: NonTerminal): BOOLEAN;
      BEGIN
      RESULT := Find(What) >= 0;
      END Includes;
      
    REDEFINE METHOD Append (El: NonTerminalRef);
      VAR
        El2: NonTerminalRef;
      BEGIN
      IF Find(El.Ref) < 0 THEN
        WHAT El.Ref OF
          IN DefinitionModule:
            IF TAG.Canonical THEN
              El2.CREATE (TAG.UniqueClass, El.SourceRef);
              Append (El2);
             ELSE
              BASE (El);
              END;
            END;
         ELSE
          BASE(El);
          END;
        END;
      END Append;
      
    METHOD DeleteRef (Ref: NonTerminal);
      VAR
        i: INTEGER;
      BEGIN
      i := Find (Ref);
      IF i >= 0 THEN
        Delete(i);
        END;
      END DeleteRef;
      
    METHOD Clone: RefList;
      BEGIN
      RESULT.CREATE;
      FOR i := 0 TO Size-1 DO
        RESULT.Append (Get(i));
        END;
      END Clone;
      
  END RefList;     
---------------------------------------------                        
  CLASS LintIdentList;
    INHERITS IdentList;
    
    REDEFINE METHOD Append(El: Ident);
      BEGIN
      ASSERT (El <> VOID);
      BASE (El);
      END Append;
    
    METHOD Refs(Id: Ident): LintIdentList;
      VAR
         First: INTEGER;
        Abort: BOOLEAN;
        Searched: ARRAY OF CHAR;
        SearchedRef: NonTerminal;
        TheId: Ident;
      BEGIN
      RESULT.CREATE;
      First := -1;
      ASSERT Id <> VOID;
      Searched := Id.Data;
      ASSERT Searched <> VOID;
      SearchedRef := Id.GetRef;
      FOR i := 0 TO Size-1 WHILE First = -1 DO
        TheId := Get(i);
        ASSERT TheId <> VOID;
        ASSERT TheId.Data <> VOID;
        IF TheId <> Id THEN
          IF (TheId.Data = Searched) AND (TheId.GetRef = SearchedRef) THEN
            First := i;
            END;
          END;
        END;
      FOR i := First TO Size-1 WHILE NOT Abort DO
        TheId := Get(i);
        IF TheId <> VOID THEN -- Even if I don't understand how Get(i)
                              -- could decently get void, ...
          IF TheId <> Id THEN                              
            IF TheId.Data = Searched THEN
              IF TheId.GetRef = SearchedRef THEN
                RESULT.Append (TheId);
                END;
             ELSE
              Abort := TRUE;
              END;
            END;
          END;
        END;
      IF RESULT.Size = 0 THEN
        RESULT := VOID;
        END;
      END Refs;
      
  END LintIdentList;      
----------------------------------------------------
  CLASS LintChecker;
      
    METHOD BuildRefList (CompUnit: CompilationUnit;
                         RList1, RList2: RefList);
                         
      METHOD AddImportClause (Import: ImportClause);
        VAR
          ElRef: NonTerminalRef;
          IdList: IdentList;
        BEGIN
        IF Import.ClassList = VOID THEN
          ElRef.CREATE (Import.Module, Import);
          RList1.Append (ElRef);
          RList2.Append (ElRef);
         ELSE
          IdList := Import.ClassList;
          FOR i := 0 TO IdList.Size - 1 DO
            ElRef.CREATE (IdList.Get(i).GetRef, IdList.Get(i));
            RList1.Append (ElRef);
            RList2.Append (ElRef);
            END;                    
          END;
        END AddImportClause;
        
      VAR                         
        l: NTList(ImportClause);
        BEGIN
      l := CompUnit.GetImportList.GetList;
      FOR i := 0 TO l.Size - 1 DO
        AddImportClause(l.Get(i));
        END;
      END BuildRefList;
      
    METHOD VisitCompilationUnit (CompUnit: CompilationUnit;
                                 RList1, 
                                 RList2: RefList);
      VAR
        Walk: Walker;                                 
        p: NonTerminal;
      BEGIN
      ASSERT CompUnit <> VOID;
      Walk.CREATE (CompUnit);
      p := Walk.Next;
      WHILE p <> VOID DO
        WHAT p OF 
          IN Ident:
            RList1.DeleteRef (TAG.GetRef);
            RList2.DeleteRef (TAG.GetRef);
            p := Walk.Next;
            END;
         ELSE
          p := Walk.Next;
          END;      
        END;
      END VisitCompilationUnit;                                 
        
    METHOD CheckImportUsage (Imp: ImplementationModule;
                             Def: DefinitionModule);
      VAR
        DefRefList, ImpRefList, DefWrkList, ImpWrkList,
        CombinedRefList, CombinedWrkList: RefList;
        Sym: NonTerminal;           
        ElRef: NonTerminalRef;
      BEGIN
      DefRefList.CREATE;
      ImpRefList.CREATE;      
      CombinedRefList.CREATE;
      BuildRefList (Def, DefRefList, CombinedRefList);         
      BuildRefList (Imp, ImpRefList, CombinedRefList); 
      DefWrkList := DefRefList.Clone;
      ImpWrkList := ImpRefList.Clone;
      CombinedWrkList := CombinedRefList.Clone;
      VisitCompilationUnit (Def, DefWrkList, CombinedWrkList);      
      VisitCompilationUnit (Imp, ImpWrkList, CombinedWrkList);      
      FOR i := 0 TO DefWrkList.Size - 1 DO
        ElRef := DefWrkList.Get(i);
        Sym := ElRef.Ref;
        IF CombinedWrkList.Includes (Sym) THEN
          ElRef.Warning ("Unused import: " + ElRef.ToString);
         ELSIF ImpRefList.Includes (Sym) THEN
          ElRef.Warning ("Redundant import: " + 
                                      ElRef.ToString);
         ELSE
          ElRef.Warning ("Used in the implementation only: " + 
                              ElRef.ToString);
          END;
        END;       
      -------------------------------
      -- Now, visit the unresolved items
      -- in the implementation module.
      -------------------------------  
      FOR i := 0 TO ImpWrkList.Size - 1 DO
        ElRef := ImpWrkList.Get(i);
        Sym := ElRef.Ref;
        IF DefRefList.Includes (Sym) AND NOT DefWrkList.Includes (Sym) THEN
          ElRef.Warning ("Redundant import: " + 
                              ElRef.ToString);
         ELSE
          ElRef.Warning ("Unused import: " + 
                              ElRef.ToString);
          END;
        END;       
      ---------------------------
      -- Check for 'valid' redundant imports
      ---------------------------
      FOR i := 0 TO ImpRefList.Size - 1 DO
        ElRef := ImpRefList.Get(i);
        Sym := ElRef.Ref;
        IF NOT ImpWrkList.Includes (Sym) THEN
          IF DefRefList.Includes (Sym) THEN
            IF NOT DefWrkList.Includes (Sym) THEN
              ElRef.Warning ("Redundant import: " + 
                              ElRef.ToString);
              END;
            END;
          END;
        END;
      END CheckImportUsage;
      
    VAR
      IdList: LintIdentList;
      MethodImpList: List(MethodImplementation);

    METHOD CheckMethodUsage (Meth: MethodImplementation);
      VAR
        q: IdentList;
        r: Ref(MethodImplementation);
        BEGIN
      ASSERT Meth <> VOID;
      IF (Meth.Definition = VOID) AND (NOT Meth.Redefine) THEN
        q := IdList.Refs(Meth.Id);
        IF q <> VOID THEN
          r.CREATE (VOID);
          FOR j := q.Size - 1 TO 0 BY -1 DO
            r.Set(VOID);
            q.Get(j).GetAncestor (r);
            IF r.Get = Meth THEN
              q.Delete (j);
              END;
            END;          
          END;
        IF (q = VOID) OR (q.Size = 0) THEN
          Meth.Warning ("Unreferenced method: " + Meth.Id.Data);
          END;
        END;
      END CheckMethodUsage;
      
    METHOD CheckRecursionUsage (Meth: MethodImplementation);
      VAR
        l: List(Desig);
        d: Desig;
        StatRef: Ref(Statement);
      BEGIN
      ASSERT Meth <> VOID;
      l.CREATE;
      StatRef.CREATE (VOID);
      IF Meth.StmtList <> VOID THEN
        Meth.StmtList.GrabSubNodes (l);  
                                -- Collect all the designators used in
                                -- the method.
        FOR i := 0 TO l.Size - 1 DO
          d := l.Get(i);
          -------------------------
          -- If the designator's first identifier refers
          -- to the enclosing method itself, it is a 
          -- recursive method invocation.
          -------------------------
          IF d.First.Id.Data = Meth.Id.Data THEN  
            StatRef.Set (VOID);
            d.GetAncestor (StatRef);  -- Collect the closest statement
                                      -- parent in the parse tree.
            ASSERT StatRef.Get <> VOID;          
            IF StatRef.Get.NestingLevel = 0 THEN
              StatRef.Get.Warning ("Excessive recursion");
              END;
            END;
          END;                              
        END;
      END CheckRecursionUsage;
      
    METHOD CheckBaseUsage (Meth: MethodImplementation);
      VAR
        l: IdentList;
        Found: BOOLEAN;
        Ref: ARRAY OF CHAR;
        OrgDecl: MethodDeclaration;
      BEGIN
      ASSERT Meth <> VOID;
      IF Meth.Redefine THEN
        l.CREATE;
        Meth.GrabSubNodes (l);
        Ref := PredefItems.Base.Id.Data;
        FOR i := 0 TO l.Size - 1 WHILE NOT Found DO
          Found := l.Get(i).Data = Ref;
          END;                        
        IF NOT Found THEN
          OrgDecl := Meth.Redefined(Original := TRUE);        
          IF OrgDecl <> VOID THEN           
            WHAT OrgDecl OF
              IN PredefMethod:
                -- Do Nothing...
                END;
             ELSE
              IF (OrgDecl <> Meth.Redefined(Original := FALSE)) OR
                 NOT OrgDecl.Deferred THEN
                Meth.Warning ("Method redefinition " +
                              "without explicit call to BASE");
                END;
              END;                            
            END;
          END;
        END;
      END CheckBaseUsage;
      
    METHOD CheckResult (Meth: MethodImplementation);
      VAR
        Id: Ident;
        SubList: IdentList;
      BEGIN    
      ASSERT Meth <> VOID;
      IF (Meth.Result <> VOID) AND (Meth.StmtList <> VOID) AND
         NOT (Meth.StmtList.IsEmpty) THEN
        Id := Meth.Result.Id;
        Id.SetRef (Meth.Result);
        SubList := IdList.Refs(Id);
        IF (SubList = VOID) OR (SubList.Size = 0) THEN
          Meth.Warning ("RESULT is never assigned");
          END;        
        END;
      END CheckResult;
     
    METHOD BuildWorkLists (Imp: ImplementationModule);
      BEGIN    
      IdList.CREATE;
      MethodImpList.CREATE;
      Imp.GrabSubNodes (IdList);
      IdList.Sort;
      Imp.GrabSubNodes (MethodImpList);
      END BuildWorkLists;
      
    REDEFINE METHOD CREATE (Main: ImplementationModule);
      VAR
        Def: DefinitionModule;
        Meth: MethodImplementation;
        BEGIN
      Def := Main.DefModule;
      ASSERT Def <> VOID;
      CheckImportUsage (Main, Def);
      BuildWorkLists(Main);
      FOR i := 0 TO MethodImpList.Size - 1 DO        
        Meth := MethodImpList.Get(i);
        CheckMethodUsage (Meth);
        CheckResult (Meth);
        CheckRecursionUsage (Meth);
        CheckBaseUsage (Meth);
        END;
      END CREATE;
      
  END LintChecker;

END YaflLint;
