(* M2Comp.mod continually calls the compiler for every source file.

Copyright (C) 2001-2025 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.

This file is part of GNU Modula-2.

GNU Modula-2 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GNU Modula-2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Modula-2; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  *)

IMPLEMENTATION MODULE M2Comp ;


FROM M2Pass IMPORT SetPassToPass0, SetPassToPass1, SetPassToPass2, SetPassToPassC,
                   SetPassToPass3, SetPassToNoPass, SetPassToPassHidden ;

FROM M2Reserved IMPORT toktype ;
FROM M2Search IMPORT FindSourceDefFile, FindSourceModFile ;
FROM M2Code IMPORT Code ;

FROM M2LexBuf IMPORT OpenSource, CloseSource, ResetForNewPass, currenttoken, GetToken,
                     ReInitialize, currentstring, GetTokenNo, BuiltinTokenNo,
                     UnknownTokenNo ;

FROM M2FileName IMPORT CalculateFileName ;
FROM M2Preprocess IMPORT PreprocessModule, MakeSaveTempsFileNameExt, OnExitDelete ;
FROM libc IMPORT exit ;

FROM M2Error IMPORT ErrorStringAt, ErrorStringAt2, ErrorStringsAt2,
                    WriteFormat0, FlushErrors, FlushWarnings, ResetErrorScope ;

FROM M2MetaError IMPORT MetaErrorString0, MetaErrorString1, MetaError0, MetaError1,
                        MetaString0 ;

FROM FormatStrings IMPORT Sprintf1 ;
FROM P0SymBuild IMPORT P0Init, P1Init ;
FROM M2Debug IMPORT Assert ;

IMPORT m2flex ;
IMPORT P0SyntaxCheck ;
IMPORT P1Build ;
IMPORT P2Build ;
IMPORT PCBuild ;
IMPORT P3Build ;
IMPORT PHBuild ;
IMPORT PCSymBuild ;
IMPORT DynamicStrings ;

FROM M2Batch IMPORT GetSource, GetModuleNo, GetDefinitionModuleFile, GetModuleFile,
                    AssociateModule, AssociateDefinition, MakeImplementationSource,
                    MakeProgramSource ;

FROM SymbolTable IMPORT GetSymName, IsDefImp, NulSym,
                        IsHiddenTypeDeclared, GetFirstUsed, GetMainModule, SetMainModule,
                        ResolveConstructorTypes, SanityCheckConstants, IsDefinitionForC,
                        IsBuiltinInModule, PutModLink, IsDefLink, IsModLink, PutLibName,
                        GetModuleDefImportStatementList, GetModuleModImportStatementList,
                        GetImportModule, IsImportStatement, IsImport,
                        GetImportStatementList ;

FROM M2Search IMPORT FindSourceDefFile ;

FROM FIO IMPORT File, StdErr, StdOut, Close, EOF, IsNoError, WriteLine,
                WriteChar, FlushOutErr ;

FROM SFIO IMPORT WriteS, OpenToRead, OpenToWrite, ReadS, WriteS ;
FROM NameKey IMPORT Name, GetKey, KeyToCharStar, makekey ;
FROM M2Printf IMPORT fprintf0, fprintf1 ;
FROM M2Quiet IMPORT qprintf0, qprintf1, qprintf2 ;

FROM M2Options IMPORT Verbose, GetM2Prefix, GetM, GetMM, GetDepTarget, GetMF, GetMP,
                      GetObj, PPonly, Statistics, Quiet, WholeProgram, GetMD, GetMMD,
                      ExtendedOpaque, GenModuleList ;

FROM PathName IMPORT DumpPathName ;
FROM Lists IMPORT List, NoOfItemsInList, GetItemFromList ;
FROM Indexing IMPORT Index, InitIndex, KillIndex, GetIndice, PutIndice, HighIndice ;

FROM DynamicStrings IMPORT String, InitString, KillString, InitStringCharStar,
                           Dup, Mark, EqualArray, string, Length, ConCat, ConCatChar,
                           InitStringChar, RIndex, Slice, Equal, RemoveWhitePrefix ;


CONST
   Debugging = FALSE ;

VAR
   ModuleType : (None, Definition, Implementation, Program) ;
   DepContent : Index ;
   DepOutput  : String ;


(*
   CompilingDefinitionModule - returns true if the current module being
                               compiled is a definition module.
*)

PROCEDURE CompilingDefinitionModule() : BOOLEAN ;
BEGIN
   RETURN( ModuleType=Definition )
END CompilingDefinitionModule ;


(*
   CompilingImplementationModule - returns true if the current module being
                                   compiled is an implementation module.
*)

PROCEDURE CompilingImplementationModule() : BOOLEAN ;
BEGIN
   RETURN( ModuleType=Implementation )
END CompilingImplementationModule ;


(*
   CompilingProgramModule - returns true if the current module being
                            compiled is a program module.
*)

PROCEDURE CompilingProgramModule() : BOOLEAN ;
BEGIN
   RETURN( ModuleType=Program )
END CompilingProgramModule ;


(*
   NeedToParseImplementation -
*)

PROCEDURE NeedToParseImplementation (sym: CARDINAL) : BOOLEAN ;
BEGIN
   RETURN (IsDefImp(sym) AND IsHiddenTypeDeclared(sym) AND ExtendedOpaque) OR
          (IsDefImp(sym) AND IsBuiltinInModule(sym)) OR
          (WholeProgram AND (NOT IsDefinitionForC(sym)))
END NeedToParseImplementation ;


(*
   GenerateDefDependency - generate a single dependency for the definition module
                           providing that it can be found and is not blocked by -MM.
*)

PROCEDURE GenerateDefDependency (module: CARDINAL) ;
VAR
   stem,
   fullpath,
   named   : String ;
BEGIN
   stem := InitStringCharStar (KeyToCharStar (GetSymName (module))) ;
   named := NIL ;
   IF FindSourceDefFile (stem, fullpath, named)
   THEN
      IF EqualArray (named, '') OR (NOT GetMM ())
      THEN
         MergeDep (DepContent, fullpath)
      ELSE
         fullpath := KillString (fullpath)
      END
   END ;
   stem := KillString (stem) ;
   named := KillString (named)
END GenerateDefDependency ;


(*
   GenerateDependenciesFromImport - lookup the module associated with the import
                                    and call GenerateDefDependency.
*)

PROCEDURE GenerateDependenciesFromImport (import: CARDINAL) ;
VAR
   module  : CARDINAL ;
BEGIN
   Assert (IsImport (import)) ;
   module := GetImportModule (import) ;
   GenerateDefDependency (module)
END GenerateDependenciesFromImport ;


(*
   GenerateDependenciesFromList - iterative over the import lists and for
                                  each module issue a dependency.
*)

PROCEDURE GenerateDependenciesFromList (dep: List) ;
VAR
   importList: List ;
   import    : CARDINAL ;
   i, n, j, m: CARDINAL ;
BEGIN
   n := NoOfItemsInList (dep) ;
   i := 1 ;
   WHILE i <= n DO
      import := GetItemFromList (dep, i) ;
      IF IsImportStatement (import)
      THEN
         importList := GetImportStatementList (import) ;
         j := 1 ;
         m := NoOfItemsInList (importList) ;
         WHILE j <= m DO
            import := GetItemFromList (importList, j) ;
            GenerateDependenciesFromImport (import) ;
            INC (j)
         END
      ELSE
         GenerateDependenciesFromImport (import)
      END ;
      INC (i)
   END
END GenerateDependenciesFromList ;


(*
   GenerateDependencies - generate a list of dependencies for the main module where
                          the source code is found in sourcefile.
*)

PROCEDURE GenerateDependencies ;
BEGIN
   IF IsDefImp (GetMainModule ())
   THEN
      GenerateDependenciesFromList (GetModuleDefImportStatementList (GetMainModule ())) ;
      GenerateDefDependency (GetMainModule ())
   END ;
   GenerateDependenciesFromList (GetModuleModImportStatementList (GetMainModule ())) ;
   WriteDepContents (DepOutput, DepContent)
END GenerateDependencies ;


(*
   Compile - compile file, s, using a 5 pass technique.
*)

PROCEDURE Compile (s: String) ;
BEGIN
   DoPass0 (s) ;
   FlushWarnings ; FlushErrors ;
   ResetForNewPass ; ResetErrorScope ;
   qprintf0('Pass 1: scopes, enumerated types, imports and exports\n') ;
   DoPass1 ;
   FlushWarnings ; FlushErrors ;
   IF GetM () OR GetMM ()
   THEN
      GenerateDependencies
   END ;
   IF NOT PPonly
   THEN
      qprintf0('Pass 2: constants and types\n') ;
      ResetForNewPass ; ResetErrorScope ;
      DoPass2 ;
      FlushWarnings ; FlushErrors ;
      qprintf0('Pass C: aggregate constants\n') ;
      ResetForNewPass ; ResetErrorScope ;
      DoPassC ;
      FlushWarnings ; FlushErrors ;
      qprintf0('Pass 3: quadruple generation\n') ;
      ResetForNewPass ; ResetErrorScope ;
      DoPass3 ;
      FlushWarnings ; FlushErrors ;
      qprintf0('Pass 4: gcc tree generation\n') ;
      Code ;
      FlushWarnings ; FlushErrors
   END
END Compile ;


(*
   compile - compile the filename.
*)

PROCEDURE compile (filename: ADDRESS) ;
VAR
   f: String ;
BEGIN
   f := InitStringCharStar (filename) ;
   Compile (f) ;
   f := KillString (f)
END compile ;


(*
   ExamineHeader - examines up until the ';', '[' or eof and determines if the source file
                   is a program, implementation/definition module.
*)

PROCEDURE ExamineHeader (VAR name: ADDRESS; VAR isdefimp, module: BOOLEAN) ;
BEGIN
   (* Stop if we see one of eof ';' '['.  *)
   WHILE (currenttoken#eoftok) AND
         (currenttoken#semicolontok) AND (currenttoken#lsbratok) DO
      IF name = NIL
      THEN
         IF (currenttoken=implementationtok) OR (currenttoken=definitiontok)
         THEN
            isdefimp := TRUE ;
            GetToken
         END ;
         IF currenttoken=moduletok
         THEN
            module := TRUE ;
            GetToken ;
            IF currenttoken=identtok
            THEN
               name := currentstring
            END
         END ;
      END ;
      GetToken
   END ;
END ExamineHeader ;


(*
   ExamineCompilationUnit - opens the source file to obtain the module name and kind of module.
*)

PROCEDURE ExamineCompilationUnit () : CARDINAL ;
VAR
   Message : String ;
   name    : ADDRESS ;
   module,
   isdefimp: BOOLEAN ;
BEGIN
   name := NIL ;
   isdefimp := FALSE ;   (* default to program module *)
   module := FALSE ;  (* Seen module keyword?  *)
   ExamineHeader (name, isdefimp, module) ;
   IF name = NIL
   THEN
      IF module
      THEN
         Message := MetaString0 (InitString ('no {%kMODULE} keyword seen'))
      ELSE
         Message := MetaString0 (InitString ('no module ident seen'))
      END ;
      m2flex.M2Error (string (Message)) ;
      exit (1)
   ELSE
      (* The token used is will be overwritten when P0 is underway.
         At this point we are determining the module kind and the tokens
         read will be discarded (see ReInitialize below).  *)
      IF isdefimp
      THEN
         RETURN MakeImplementationSource (BuiltinTokenNo, makekey (name))
      ELSE
         RETURN MakeProgramSource (BuiltinTokenNo, makekey (name))
      END
   END
END ExamineCompilationUnit ;


(*
   PeepInto - peeps into source, s, and initializes a definition/implementation or
              program module accordingly.
*)

PROCEDURE PeepInto (s: String) ;
VAR
   mainModule: CARDINAL ;
BEGIN
   IF OpenSource (s)
   THEN
      mainModule := ExamineCompilationUnit () ;
      IF mainModule # NulSym
      THEN
         SetMainModule (mainModule)
      END ;
      CloseSource ;
      ReInitialize
   ELSE
      fprintf1 (StdErr, 'failed to open %s\n', s) ;
      exit (1)
   END
END PeepInto ;


(*
   qprintLibName - print the libname.
*)

PROCEDURE qprintLibName (LibName: String) ;
BEGIN
   IF (LibName # NIL) AND (NOT EqualArray (LibName, ''))
   THEN
      qprintf1 (' [%s]', LibName)
   END
END qprintLibName ;


(*
   CreateFileStem - create a stem using the template LibName_ModuleName.
*)

PROCEDURE CreateFileStem (SymName, LibName: String) : String ;
BEGIN
   IF Length (LibName) > 0
   THEN
      RETURN ConCat (Dup (LibName), ConCat (InitStringChar ('_'), SymName))
   ELSE
      RETURN SymName
   END
END CreateFileStem ;


(*
   Return basename of path.  CutExt determines whether the .extension
   should be removed.
*)

PROCEDURE BaseName (Path: String; CutExt: BOOLEAN) : String ;
VAR
   ext,
   basename: INTEGER ;
BEGIN
   basename := RIndex (Path, '/', 0) ;
   IF basename = -1
   THEN
      basename := 0
   ELSE
      basename := basename + 1
   END ;
   IF CutExt
   THEN
      ext := RIndex (Path, '.', 0) ;
      IF ext=-1
      THEN
         ext := 0
      END
   ELSE
      ext := 0
   END ;
   RETURN Slice (Path, basename, ext)
END BaseName ;


(*
   IsLibrary - return TRUE if line contains a library module.
*)

PROCEDURE IsLibrary (line: String) : BOOLEAN ;
VAR
   moduleName,
   libname, filename: String ;
   result           : BOOLEAN ;
BEGIN
   result := FALSE ;
   moduleName := BaseName (line, TRUE) ;
   filename := NIL ;
   libname := NIL ;
   IF FindSourceDefFile (moduleName, filename, libname)
   THEN
      moduleName := KillString (moduleName) ;
      IF Length (libname) > 0
      THEN
         moduleName := BaseName (line, FALSE) ;
         line := BaseName (line, FALSE) ;
         result := Equal (line, moduleName) ;
         line := KillString (line) ;
      END
   END ;
   libname := KillString (libname) ;
   filename := KillString (filename) ;
   moduleName := KillString (moduleName) ;
   RETURN result
END IsLibrary ;


(*
   IsUnique - return TRUE if line is unique in array content.
*)

PROCEDURE IsUnique (content: Index; line: String) : BOOLEAN ;
VAR
   high, i: CARDINAL ;
BEGIN
   high := HighIndice (content) ;
   i := 1 ;
   WHILE i <= high DO
      IF Equal (line, GetIndice (content, i))
      THEN
         RETURN FALSE
      END ;
      INC (i)
   END ;
   RETURN TRUE
END IsUnique ;


(*
   Append - append line to array content.
*)

PROCEDURE Append (content: Index; line: String) ;
VAR
   high: CARDINAL ;
BEGIN
   high := HighIndice (content) ;
   PutIndice (content, high+1, line)
END Append ;


(*
   MergeDep - if line is unique in array content then append.
              Check to see (and ignore) if line is a library module and -MM
              is present.
*)

PROCEDURE MergeDep (content: Index; line: String) ;
BEGIN
   line := RemoveWhitePrefix (line) ;
   IF (NOT EqualArray (line, "\")) AND (Length (line) > 0)
   THEN
      (* Ignore if -MM and is a library module.  *)
      IF NOT (GetMM () AND IsLibrary (line))
      THEN
         IF IsUnique (content, line)
         THEN
            Append (content, line)
         END
      END
   END
END MergeDep ;


(*
   splitLine - split a line into words separated by spaces
               and call MergeDep on each word.
*)

PROCEDURE splitLine (content: Index; line: String) ;
VAR
   word : String ;
   space: INTEGER ;
BEGIN
   REPEAT
      line := RemoveWhitePrefix (line) ;
      space := DynamicStrings.Index (line, ' ', 0) ;
      IF space > 0
      THEN
         word := Slice (line, 0, space) ;
         word := RemoveWhitePrefix (word) ;
         IF Length (word) > 0
         THEN
            MergeDep (content, word)
         END ;
         line := Slice (line, space, 0) ;
      ELSIF space < 0
      THEN
         MergeDep (content, line)
      END
   UNTIL space <= 0
END splitLine ;


(*
   MergeDeps - foreach dependency in ChildDep do
                  add dependency to ChildDep if not already present.
               ignore all ChildDep if -MM and libname # "".
*)

PROCEDURE MergeDeps (content: Index; ChildDep, LibName: String) ;
VAR
   line: String ;
   in  : File ;
BEGIN
   IF (content # NIL) AND (NOT (GetMM () AND (Length (LibName) > 0)))
   THEN
      in := OpenToRead (ChildDep) ;
      IF IsNoError (in)
      THEN
         line := ReadS (in) ;  (* Skip over first line containing the module object.  *)
         WHILE NOT EOF (in) DO
            line := ReadS (in) ;
            splitLine (content, line)
         END
      END ;
      Close (in)
   END
END MergeDeps ;


(*
   GetRuleTarget - return the rule target which is derived from the -MT arg
                   or -o arg or filename.mod.
*)

PROCEDURE GetRuleTarget (filename: String) : String ;
BEGIN
   IF GetDepTarget () # NIL
   THEN
      RETURN InitStringCharStar (GetDepTarget ())
   ELSIF GetMF () # NIL
   THEN
      RETURN InitStringCharStar (GetMF ())
   ELSE
      RETURN ConCat (BaseName (filename, TRUE), InitString ('.o'))
   END
END GetRuleTarget ;


(*
   ReadDepContents - reads the contents of file dep into a dynamic array
                     and return the array.  The file will be split into words
                     and each word stored as an entry in the array.
*)

PROCEDURE ReadDepContents (filename, dep: String) : Index ;
VAR
   content: Index ;
   line   : String ;
   in     : File ;
BEGIN
   content := NIL ;
   IF GetM () OR GetMM ()
   THEN
      in := OpenToRead (dep) ;
      (* The file might not be created (if -MD or -MMD is used as these options
         operate without preprocessing) in which case we create an dynamic
         array with the source filename and target.  *)
      content := InitIndex (1) ;
      IF GetMD () OR GetMMD () OR (NOT IsNoError (in))
      THEN
         (* No preprocessing done therefore create first two lines using
            target and source.  *)
         PutIndice (content, 1, ConCatChar (GetRuleTarget (filename), ':')) ;
         PutIndice (content, 2, Dup (filename))
      ELSE
         (* Preprocessing (using cc1) has created one for us, so we read it.  *)
         WHILE NOT EOF (in) DO
            line := ReadS (in) ;
            splitLine (content, line)
         END
      END ;
      Close (in)
   END ;
   RETURN content
END ReadDepContents ;


(*
   WriteDep - write the dependencies and target to file out.
*)

PROCEDURE WriteDep (contents: Index; out: File) ;
VAR
   i, h: CARDINAL ;
   line: String ;
BEGIN
   i := 1 ;
   h := HighIndice (contents) ;
   WHILE i <= h DO
      line := GetIndice (contents, i) ;
      line := RemoveWhitePrefix (line) ;
      IF Length (line) > 0
      THEN
         IF i = 1
         THEN
            (* First line is always the target.  *)
            IF GetDepTarget () # NIL
            THEN
               line := ConCatChar (InitStringCharStar (GetDepTarget ()), ':')
            END
         ELSIF i > 1
         THEN
            WriteChar (out, ' ')
         END ;
         line := WriteS (out, line) ;
         IF i < h
         THEN
            WriteChar (out, ' ') ;
            WriteChar (out, '\')
         END ;
         WriteLine (out)
      END ;
      INC (i)
   END
END WriteDep ;


(*
   WritePhonyDep - write the dependencies and target to file out.
*)

PROCEDURE WritePhonyDep (contents: Index; out: File) ;
VAR
   i, h: CARDINAL ;
   line: String ;
BEGIN
   (* The first line is always the target and the second line is always
      the top level source file.  *)
   i := 3 ;
   h := HighIndice (contents) ;
   WHILE i <= h DO
      line := GetIndice (contents, i) ;
      line := RemoveWhitePrefix (line) ;
      IF Length (line) > 0
      THEN
         line := WriteS (out, line) ;
         WriteChar (out, ':') ;
         WriteLine (out)
      END ;
      INC (i)
   END
END WritePhonyDep ;


(*
   WriteDepContents - write the dynamic array to filename dep (or StdOut) if
                      the GetMF file is NIL.
*)

PROCEDURE WriteDepContents (dep: String; contents: Index) ;
VAR
   out: File ;
BEGIN
   IF (contents # NIL) AND (GetM () OR GetMM ())
   THEN
      IF GetMF () = NIL
      THEN
         out := StdOut ;
         dep := OnExitDelete (dep)
      ELSE
         out := OpenToWrite (dep)
      END ;
      IF IsNoError (out)
      THEN
         WriteDep (contents, out) ;
         IF GetMP ()
         THEN
            WritePhonyDep (contents, out)
         END
      END ;
      IF GetMF () = NIL
      THEN
         FlushOutErr
      ELSE
         Close (out) ;
      END ;
      contents := KillIndex (contents)
   END
END WriteDepContents ;


(*
   CreateDepFilename - return a dependency filename associated with filename or use GetMF.
*)

PROCEDURE CreateDepFilename (filename: String) : String ;
VAR
   depfile: String ;
BEGIN
   IF GetMF () = NIL
   THEN
      depfile := MakeSaveTempsFileNameExt (filename, InitString ('.d')) ;
      RETURN OnExitDelete (depfile)
   ELSE
      RETURN InitStringCharStar (GetMF ())
   END
END CreateDepFilename ;


(*
   Pass0CheckDef -
*)

PROCEDURE Pass0CheckDef (sym: CARDINAL) : BOOLEAN ;
VAR
   ChildDep,
   SymName,
   FileName,
   LibName : String ;
BEGIN
   LibName := NIL ;
   FileName := NIL ;
   SymName := InitStringCharStar (KeyToCharStar (GetSymName (sym))) ;
   IF IsDefImp (sym)
   THEN
      IF FindSourceDefFile (SymName, FileName, LibName)
      THEN
         ModuleType := Definition ;
         ChildDep := MakeSaveTempsFileNameExt (CreateFileStem (SymName, LibName), InitString ('.def.d')) ;
         IF OpenSource (AssociateDefinition (PreprocessModule (FileName, FALSE, TRUE,
                                                               ChildDep), sym))
         THEN
            IF NOT P0SyntaxCheck.CompilationUnit ()
            THEN
               WriteFormat0 ('compilation failed') ;
               CloseSource ;
               SymName := KillString (SymName) ;
               FileName := KillString (FileName) ;
               LibName := KillString (LibName) ;
               RETURN FALSE
            END ;
            qprintf2 ('   Module %-20s : %s', SymName, FileName) ;
            qprintLibName (LibName) ;
            PutLibName (sym, makekey (string (LibName))) ;
            IF IsDefinitionForC (sym)
            THEN
               qprintf0 (' (for C)')
            END ;
            IF IsDefLink (sym)
            THEN
               qprintf0 (' (linking)')
            END ;
            qprintf0 ('\n') ;
            CloseSource ;
            MergeDeps (DepContent, ChildDep, LibName)
         ELSE
            (* Unrecoverable error.  *)
            MetaErrorString1 (Sprintf1 (InitString ('file {%%1EUAF%s} containing module {%%1a} cannot be found'),
                                        FileName), sym)
         END
      ELSE
         (* Unrecoverable error.  *)
         MetaError1 ('the file containing the definition module {%1EMAa} cannot be found', sym)
      END ;
      ModuleType := Implementation
   ELSE
      ModuleType := Program
   END ;
   SymName := KillString (SymName) ;
   FileName := KillString (FileName) ;
   LibName := KillString (LibName) ;
   RETURN TRUE
END Pass0CheckDef ;


(*
   Pass0CheckMod -
*)

PROCEDURE Pass0CheckMod (sym: CARDINAL; PPSource: String) : BOOLEAN ;
VAR
   Main    : CARDINAL ;
   ChildDep,
   SymName,
   FileName,
   LibName : String ;
BEGIN
   SymName := InitStringCharStar (KeyToCharStar (GetSymName (sym))) ;
   FileName := NIL ;
   LibName := NIL ;
   Main := GetMainModule () ;
   IF (Main = sym) OR NeedToParseImplementation (sym)
   THEN
      (* Only need to read implementation module if hidden types are
         declared or it is the main module.  *)
      IF Main = sym
      THEN
         FileName := Dup (PPSource) ;
         LibName := InitStringCharStar (GetM2Prefix ()) ;
         PutLibName (sym, makekey (string (LibName)))
      ELSE
         IF FindSourceModFile (SymName, FileName, LibName)
         THEN
            ChildDep := MakeSaveTempsFileNameExt (CreateFileStem (SymName, LibName), InitString ('.mod.d')) ;
            FileName := PreprocessModule (FileName, FALSE, TRUE, ChildDep) ;
            PutLibName (sym, makekey (string (LibName))) ;
            MergeDeps (DepContent, ChildDep, LibName)
         ELSE
            qprintf1 ('   Module %-20s : implementation source file not found\n', SymName)
         END
      END ;

      IF FileName # NIL
      THEN
         IF OpenSource (AssociateModule (Dup (FileName), sym))
         THEN
            IF NOT P0SyntaxCheck.CompilationUnit ()
            THEN
               WriteFormat0 ('compilation failed') ;
               CloseSource ;
               SymName := KillString (SymName) ;
               FileName := KillString (FileName) ;
               LibName := KillString (LibName) ;
               RETURN FALSE
            END ;
            qprintf2 ('   Module %-20s : %s', SymName, FileName) ;
            qprintLibName (LibName) ;
            IF IsModLink (sym)
            THEN
               qprintf0 (' (linking)')
            END ;
            qprintf0 ('\n') ;
            CloseSource
         ELSE
            (* It is quite legitimate to implement a module in C (and pretend it was a M2
               implementation) providing that it is not the main program module and the
               definition module does not declare a hidden type when -fextended-opaque
               is used.  *)
            IF (NOT WholeProgram) OR (sym = Main) OR IsHiddenTypeDeclared (sym)
            THEN
               (* Unrecoverable error.  *)
               MetaErrorString1 (Sprintf1 (InitString ('file {%%1EUAF%s} containing module {%%1a} cannot be found'),
                                           FileName), sym) ;
            END
         END
      END
   ELSIF GenModuleList
   THEN
      IF IsDefImp (sym) AND (NOT IsDefinitionForC (sym))
      THEN
         (* The implementation module is only useful if -fgen-module-list= is
            used (to gather all dependencies).  Note that we do not insist
            upon finding the implementation module.  *)
         LibName := NIL ;
         IF FindSourceModFile (SymName, FileName, LibName)
         THEN
            PutLibName (sym, makekey (string (LibName))) ;
            qprintf2 ('   Module %-20s : %s' , SymName, FileName) ;
            qprintLibName (LibName) ;
            qprintf0 (' (linking)\n') ;
            ChildDep := MakeSaveTempsFileNameExt (CreateFileStem (SymName, LibName), InitString ('.mod.d')) ;
            IF OpenSource (AssociateModule (PreprocessModule (FileName, FALSE, TRUE, ChildDep), sym))
            THEN
               PutModLink (sym, TRUE) ;   (* This source is only used to determine link time info.  *)
               IF NOT P0SyntaxCheck.CompilationUnit ()
               THEN
                  WriteFormat0 ('compilation failed') ;
                  CloseSource ;
                  SymName := KillString (SymName) ;
                  FileName := KillString (FileName) ;
                  LibName := KillString (LibName) ;
                  RETURN FALSE
               END ;
               CloseSource ;
               MergeDeps (DepContent, ChildDep, LibName)
            END
         END
      END
   END ;
   SymName := KillString (SymName) ;
   FileName := KillString (FileName) ;
   LibName := KillString (LibName) ;
   RETURN TRUE
END Pass0CheckMod ;


(*
   DoPass0 -
*)

PROCEDURE DoPass0 (filename: String) ;
VAR
   sym       : CARDINAL ;
   i         : CARDINAL ;
   PPSource  : String ;
BEGIN
   P0Init ;
   SetPassToPass0 ;
   (* Maybe preprocess the main file.  *)
   DepOutput := CreateDepFilename (filename) ;
   PPSource := PreprocessModule (filename, TRUE, FALSE, DepOutput) ;
   DepContent := ReadDepContents (filename, DepOutput) ;
   PeepInto (PPSource) ;
   i := 1 ;
   sym := GetModuleNo (i) ;
   qprintf1 ('Compiling: %s\n', PPSource) ;
   IF Debugging
   THEN
      DumpPathName ('DoPass0')
   END ;
   IF Verbose
   THEN
      fprintf1 (StdOut, 'Compiling: %s\n', PPSource)
   END ;
   qprintf0 ('Pass 0: lexical analysis, parsing, modules and associated filenames\n') ;
   WHILE sym # NulSym DO
      IF NOT Pass0CheckDef (sym)
      THEN
         RETURN
      END ;
      IF NOT Pass0CheckMod (sym, PPSource)
      THEN
         RETURN
      END ;
      INC (i) ;
      sym := GetModuleNo (i)
   END ;
   SetPassToNoPass
END DoPass0 ;


(*
   DoPass1 - parses the sources of all modules necessary to compile
             the required module, Main.
*)

PROCEDURE DoPass1 ;
VAR
   name    : Name ;
   Sym     : CARDINAL ;
   i       : CARDINAL ;
   FileName: String ;
BEGIN
   P1Init ;
   SetPassToPass1 ;
   i := 1 ;
   Sym := GetModuleNo(i) ;
   WHILE Sym#NulSym DO
      FileName := GetDefinitionModuleFile(Sym) ;
      IF FileName#NIL
      THEN
         IF Debugging
         THEN
            name := GetSymName(Sym) ;
            qprintf1('   Module %a\n', name)
         END ;
         IF OpenSource(FileName)
         THEN
            ModuleType := Definition ;
            IF NOT P1Build.CompilationUnit()
            THEN
               MetaError0('compilation failed') ;
               CloseSource ;
               RETURN
            END ;
            CloseSource
         ELSE
            fprintf1(StdErr, 'failed to open %s\n', FileName) ;
            exit(1)
         END ;
         ModuleType := Implementation
      ELSE
         ModuleType := Program
      END ;
      FileName := GetModuleFile(Sym) ;
      IF FileName#NIL
      THEN
         IF Debugging
         THEN
            name := GetSymName(Sym) ;
            qprintf1('   Module %a\n', name)
         END ;
         IF OpenSource(FileName)
         THEN
            IF NOT P1Build.CompilationUnit()
            THEN
               MetaError0('compilation failed') ;
               CloseSource ;
               RETURN
            END ;
            CloseSource
         ELSE
            fprintf1(StdErr, 'failed to open %s\n', FileName) ;
            exit(1)
         END
      END ;
      INC(i) ;
      Sym := GetModuleNo(i)
   END ;
   SetPassToNoPass
END DoPass1 ;


(*
   DoPass2 - parses the sources of all modules necessary to compile
             the required module, Main.
*)

PROCEDURE DoPass2 ;
VAR
   name    : Name ;
   Sym     : CARDINAL ;
   i       : CARDINAL ;
   FileName: String ;
BEGIN
   SetPassToPass2 ;
   i := 1 ;
   Sym := GetModuleNo(i) ;
   WHILE Sym#NulSym DO
      FileName := GetDefinitionModuleFile(Sym) ;
      IF FileName#NIL
      THEN
         IF Debugging
         THEN
            name := GetSymName(Sym) ;
            qprintf1('   Module %a\n', name)
         END ;
         IF OpenSource(FileName)
         THEN
            ModuleType := Definition ;
            IF NOT P2Build.CompilationUnit()
            THEN
               MetaError0('compilation failed') ;
               CloseSource ;
               RETURN
            END ;
            CloseSource
         ELSE
            fprintf1(StdErr, 'failed to open %s\n', FileName) ;
            exit(1)
         END ;
         ModuleType := Implementation
      ELSE
         ModuleType := Program
      END ;
      FileName := GetModuleFile(Sym) ;
      IF FileName#NIL
      THEN
         IF Debugging
         THEN
            name := GetSymName(Sym) ;
            qprintf1('   Module %a\n', name)
         END ;
         IF OpenSource(FileName)
         THEN
            IF NOT P2Build.CompilationUnit()
            THEN
               MetaError0('compilation failed') ;
               CloseSource ;
               RETURN
            END ;
            CloseSource
         ELSE
            fprintf1(StdErr, 'failed to open %s\n', FileName) ;
            exit(1)
         END
      END ;
      INC(i) ;
      Sym := GetModuleNo(i)
   END ;
   SetPassToNoPass
END DoPass2 ;


(*
   DoPassC - parses the sources of all modules necessary to compile
             the required module, Main.
*)

PROCEDURE DoPassC ;
VAR
   name    : Name ;
   Sym     : CARDINAL ;
   i       : CARDINAL ;
   FileName: String ;
BEGIN
   SetPassToPassC ;
   i := 1 ;
   Sym := GetModuleNo(i) ;
   WHILE Sym#NulSym DO
      FileName := GetDefinitionModuleFile(Sym) ;
      IF FileName#NIL
      THEN
         IF Debugging
         THEN
            name := GetSymName(Sym) ;
            qprintf1('   Module %a\n', name)
         END ;
         IF OpenSource(FileName)
         THEN
            ModuleType := Definition ;
            IF NOT PCBuild.CompilationUnit()
            THEN
               MetaError0('compilation failed') ;
               CloseSource ;
               RETURN
            END ;
            CloseSource
         ELSE
            fprintf1(StdErr, 'failed to open %s\n', FileName) ;
            exit(1)
         END ;
         ModuleType := Implementation
      ELSE
         ModuleType := Program
      END ;
      FileName := GetModuleFile(Sym) ;
      IF FileName#NIL
      THEN
         IF Debugging
         THEN
            name := GetSymName(Sym) ;
            qprintf1('   Module %a\n', name)
         END ;
         IF OpenSource(FileName)
         THEN
            IF NOT PCBuild.CompilationUnit()
            THEN
               MetaError0('compilation failed') ;
               CloseSource ;
               RETURN
            END ;
            CloseSource
         ELSE
            fprintf1(StdErr, 'failed to open %s\n', FileName) ;
            exit(1)
         END
      END ;
      INC(i) ;
      Sym := GetModuleNo(i)
   END ;
   PCSymBuild.ResolveConstTypes ;
   ResolveConstructorTypes ;
   SanityCheckConstants ;
   SetPassToNoPass
END DoPassC ;


(*
   DoPass3 - parses the sources of all modules necessary to compile
             the required module, Main.
*)

PROCEDURE DoPass3 ;
VAR
   Main,
   Sym     : CARDINAL ;
   i       : CARDINAL ;
   FileName: String ;
BEGIN
   SetPassToPass3 ;
   Main := GetMainModule() ;
   i := 1 ;
   Sym := GetModuleNo(i) ;
   WHILE Sym#NulSym DO
      FileName := GetDefinitionModuleFile(Sym) ;
      IF FileName#NIL
      THEN
         IF OpenSource(FileName)
         THEN
            ModuleType := Definition ;
            IF NOT P3Build.CompilationUnit()
            THEN
               MetaError0('compilation failed') ;
               CloseSource ;
               RETURN
            END ;
            CloseSource
         ELSE
            fprintf1(StdErr, 'failed to open %s\n', FileName) ;
            exit(1)
         END ;
         ModuleType := Implementation
      ELSE
         ModuleType := Program
      END ;
      FileName := GetModuleFile(Sym) ;
      IF FileName#NIL
      THEN
         IF OpenSource(FileName)
         THEN
            IF (Main=Sym) OR WholeProgram
            THEN
               IF NOT P3Build.CompilationUnit()
               THEN
                  MetaError0('compilation failed') ;
                  CloseSource ;
                  RETURN
               END
            ELSE
               (*
                  not the main module .mod therefore must be implementing
                  a hidden type - we dont want to generate any
                  StatementSequence quadrupes but we do want to build TYPEs
                  and ConstExpressions.
               *)
               SetPassToNoPass ;
               SetPassToPassHidden ;
               IF NOT PHBuild.CompilationUnit()
               THEN
                  MetaError0('compilation failed') ;
                  CloseSource ;
                  RETURN
               END ;
               SetPassToNoPass ;
               SetPassToPass3
            END ;
            CloseSource
         ELSE
            fprintf1(StdErr, 'failed to open %s\n', FileName) ;
            exit(1)
         END
      END ;
      INC(i) ;
      Sym := GetModuleNo(i)
   END ;
   SetPassToNoPass
END DoPass3 ;


BEGIN
   ModuleType := None ;
   DepContent := NIL ;
   DepOutput := NIL
END M2Comp.