[net.lang.mod2] make

kpk@gitpyr.UUCP (Kevin P. Kleinfelter) (08/31/86)

I am posting source to a simple version of "make" written in Modula-2.
Feel free to use and modify it.  If you improve it, please post your
improvements.

kpk@gitpyr.UUCP (Kevin P. Kleinfelter) (08/31/86)

Batch file for "make".  Store this as "make.bat"
----- CUT HERE -----
m2 \sys\make %1
if not errorlevel 0 goto error
$make.bat
:error
pause error has occured

kpk@gitpyr.UUCP (Kevin P. Kleinfelter) (08/31/86)

Compile the following, and store the ".lod" file as "\sys\make.lod".
----- CUT HERE ------

MODULE Make;
(* A simple version of the UNIX "make" utility.

Handles only the following input lines

file1 : file2 file3 ... ; action
file1 : file2
file1 : ; action



To use this program you must create a \SYS directory and
place "make.bat" in it.  This program
creates and executes $MAKE.BAT in the \SYS directory.

To invoke the program type

make makefile

or

make makefile/n

(The /n option may have no leading blanks, and performs no updates.
*)


IMPORT Options;

FROM SYSTEM IMPORT
   ADR,
   DOSCALL;

FROM Storage IMPORT
   ALLOCATE;



IMPORT ASCII;



FROM InOut IMPORT
   Read,
   ReadString,
   termCH,
   WriteCard,
   WriteString,
   WriteLn;



FROM ErrorCode IMPORT
   SetErrorCode;



FROM FileSystem IMPORT
   ReadChar,
   WriteChar,
   WriteNBytes,
   File,
   Close,
   Response,
   Lookup;



CONST
   MaximumNumberOfFiles = 100;
   StringLength = 100;



TYPE
   TimeStamp = RECORD
      Date:CARDINAL;
      Time:CARDINAL;
   END; (* TimeStamp *)



   StringType = ARRAY [0..StringLength] OF CHAR;

   TableIndexType = [1..MaximumNumberOfFiles];

   DependencyPointer = POINTER TO DependencyNodeType;
   DependencyNodeType = RECORD
      DependsOn:TableIndexType;
      Next:DependencyPointer;
   END; (* DependencyNodeType *)


   ActionPointer = POINTER TO ActionRecord;
   ActionRecord = RECORD
      Action:StringType;
      Next:ActionPointer;
   END; (* ActionRecord *)



   TableNode = RECORD
      FileName: StringType;
      FileDate:TimeStamp;                (* Date and time *)
      ActionList:ActionPointer;
      DependencyList:DependencyPointer;  (* files this one depends on *)
      IsARoot:BOOLEAN;                   (* No one depends on this one *)
      SubgraphChecked:BOOLEAN;
   END; (* TableNode *)



VAR
   MakeFileName:StringType;
   OutputFile:File;
   CurrentTimeStamp:TimeStamp;
   Table:ARRAY TableIndexType OF TableNode; (* Table of dependencies *)
   FreeIndex:TableIndexType;                (* Index of first free in Table *)

   (* "switch" values *)
   NoExecute:BOOLEAN;


PROCEDURE Error(S:ARRAY OF CHAR);
BEGIN (* Error *)
   WriteString(S);
   WriteLn;
   SetErrorCode(1);
END Error;






PROCEDURE Equal(S1,S2:ARRAY OF CHAR):BOOLEAN;
VAR
   I:CARDINAL;
BEGIN (* Equal *)
   I := 0;
   LOOP
      IF (I > HIGH (S1)) THEN
         IF (I > HIGH (S2)) THEN
            RETURN TRUE;
         ELSE
            RETURN FALSE;
         END; (* IF *)
      ELSIF I > HIGH (S2) THEN
         RETURN FALSE;
      END; (* IF *)
      IF S1[I] = S2[I] THEN
         IF S1[I] = 0C THEN
            RETURN TRUE;
         END; (* IF *)
         INC(I);
      ELSE
         RETURN FALSE;
      END; (* IF *)
   END; (* LOOP *)
END Equal;


PROCEDURE GetTimeStamp(Name:ARRAY OF CHAR; VAR S:TimeStamp);
VAR
   Handle:CARDINAL;
   ErrorC:CARDINAL;
BEGIN (* GetTimeStamp *)
   (* Create a file handle *)
   DOSCALL(3DH, ADR(Name), 00, Handle, ErrorC);
   IF ErrorC = 0 THEN
   ELSIF ErrorC = 2 THEN
      S.Date := 0;
      S.Time := 0;
      RETURN;
   ELSE
      HALT;
   END; (* IF *)
   (* Get time stamp *)
   DOSCALL(57H, Handle, 00, S.Date, S.Time, ErrorC);
   IF ErrorC # 0 THEN
      HALT;
   END; (* IF *)
   (* Close handle *)
   DOSCALL(3EH, Handle, ErrorC);
   IF ErrorC # 0 THEN
      HALT;
   END; (* IF *)
END GetTimeStamp;


PROCEDURE PlaceInTable (F:StringType; VAR Ix:TableIndexType);
VAR
   I:TableIndexType;
BEGIN (* PlaceInTable *)
   I := 1;
   WHILE I < FreeIndex DO
      IF Equal(F,Table[I].FileName) THEN
         Ix := I;
         RETURN;
      END; (* IF *)
      INC (I);
   END; (* WHILE *)

   WITH Table[FreeIndex] DO
      FileName := F;
      GetTimeStamp(F,FileDate);
      ActionList := NIL;
      DependencyList := NIL;
      IsARoot := TRUE;
      SubgraphChecked := FALSE;
   END; (* WITH *)
   Ix := FreeIndex;
   INC (FreeIndex);
END PlaceInTable;



PROCEDURE ProcessMakeFile(Name:StringType);
VAR
   F:File;
   Token1:StringType;
   Token2:StringType;
   Ch:CHAR;
   I:CARDINAL;

   PROCEDURE ReadName(VAR S:StringType);
   VAR
      Ix:CARDINAL;
   BEGIN (* ReadName *)
      Ix := 0;
      WHILE NOT F.eof & (Ch # ASCII.EOL) & (Ch # ':') & (Ch # ' ') DO
         S[Ix] := Ch;
         INC(Ix);
         ReadChar(F,Ch);
      END; (* WHILE *)
      S[Ix] := 0C;
   END ReadName;

BEGIN (* ProcessMakeFile *)
   Lookup(F, Name, FALSE);
   IF NOT (F.res = done) THEN
      Error('make file not found');
      RETURN;
   END; (* IF *)
   ReadChar(F,Ch);
   WHILE NOT F.eof DO
      ReadName(Token1);
      IF F.eof THEN
         Error('Illegal line in makefile (1)');
         Close(F);
         RETURN;
      END; (* IF *)

      WHILE (Ch # ':') AND (Ch # ';') AND NOT F.eof DO
         ReadChar(F,Ch);
      END; (* WHILE *)
      IF F.eof THEN
         Error('Illegal line in makefile (2)');
         Close(F);
         RETURN;
      END; (* IF *)
      IF Ch = ':' THEN
         Ch := ' ';
      END; (* IF *)
      WHILE (Ch # ASCII.EOL) AND (NOT F.eof) AND (Ch # ';') DO
         WHILE (Ch = ' ') AND (NOT F.eof) DO
            ReadChar(F,Ch);
         END; (* WHILE *)
         IF Ch # ';' THEN
            ReadName(Token2);
            AddDependency(Token1,Token2);
         END; (* IF *)
      END; (* WHILE *)
      IF Ch = ';' THEN
         Ch := ' ';
         WHILE (Ch = ' ') AND (NOT F.eof) DO
            ReadChar(F,Ch);
         END; (* WHILE *)
         I := 0;
         WHILE (Ch # ASCII.EOL) AND (NOT F.eof) DO
            Token2[I] := Ch;
            INC(I);
            ReadChar(F,Ch);
         END; (* WHILE *)
         Token2[I] := 0C;
         IF I # 0 THEN
            AddAction(Token1,Token2);
         END; (* IF *)
      END; (* IF *)
      IF Ch = ASCII.EOL THEN
         ReadChar(F,Ch);
      END; (* IF *)
   END; (* WHILE *)
   Close(F);
   TraverseAllTrees;
END ProcessMakeFile;



PROCEDURE TimeStampGreaterThan (TS1,TS2:TimeStamp):BOOLEAN;
BEGIN (* TimeStampGreaterThan *)
   IF TS1.Date > TS2.Date THEN
      RETURN TRUE;
   ELSIF TS1.Date < TS2.Date THEN
      RETURN FALSE
   ELSE
      RETURN TS1.Time > TS2.Time
   END; (* IF *)
END TimeStampGreaterThan;



PROCEDURE TraverseThisTree(Root:TableIndexType);
VAR
   I:TableIndexType;
   Tmp:DependencyPointer;
   ActionNeeded:BOOLEAN;
BEGIN (* TraverseThisTree *)
   IF Table[Root].SubgraphChecked THEN
      RETURN;
   END; (* IF *)
   Table[Root].SubgraphChecked := TRUE;
   Tmp := Table[Root].DependencyList;
   ActionNeeded := FALSE;
   WHILE Tmp # NIL DO
      TraverseThisTree(Tmp^.DependsOn);
      IF NOT TimeStampGreaterThan(Table[Root].FileDate,
                                  Table[Tmp^.DependsOn].FileDate) THEN
         Table[Root].FileDate := CurrentTimeStamp;
         ActionNeeded := TRUE;
      END; (* IF *)
      Tmp := Tmp^.Next;
   END; (* WHILE *)
   IF ActionNeeded THEN
      TakeTheseActions(Root);
   END; (* IF *)
END TraverseThisTree;


PROCEDURE AddAction(F:StringType;A:StringType);
VAR
   Ix:TableIndexType;
   Temp:ActionPointer;
BEGIN (* AddAction *)
   PlaceInTable(F,Ix);
   NEW(Temp);
   Temp^.Next := Table[Ix].ActionList;
   Temp^.Action := A;
   Table[Ix].ActionList := Temp;
END AddAction;


PROCEDURE TraverseAllTrees;
VAR
   Ix:TableIndexType;
BEGIN (* TraverseAllTrees *)
   FOR Ix := 1 TO (FreeIndex - 1) DO
      IF Table[Ix].IsARoot THEN
         TraverseThisTree(Ix);
      END; (* IF *)
   END; (* FOR *)
END TraverseAllTrees;


PROCEDURE AddDependency (F1,F2:StringType);
VAR
   Ix1:TableIndexType;
   Ix2:TableIndexType;
   Temp:DependencyPointer;
BEGIN (* AddDependency *)
   PlaceInTable(F1,Ix1);
   PlaceInTable(F2,Ix2);
   IF DependencyExists(Ix1,Ix2) THEN
      RETURN;
   END; (* IF *)
   NEW (Temp);
   Temp^.DependsOn := Ix2;
   Temp^.Next := Table[Ix1].DependencyList;
   Table[Ix1].DependencyList := Temp;
   Table[Ix2].IsARoot := FALSE;
END AddDependency; 



PROCEDURE DependencyExists(Ix1,Ix2:TableIndexType):BOOLEAN;
VAR
   Temp:DependencyPointer;
BEGIN (* DependencyExists *)
   Temp := Table[Ix1].DependencyList;
   WHILE (Temp # NIL) & (Temp^.DependsOn # Ix2) DO
      Temp := Temp^.Next;
   END; (* WHILE *)
   RETURN (Temp # NIL);
END DependencyExists;



PROCEDURE WriteCharacterString(S:ARRAY OF CHAR);
VAR
   Dummy:CARDINAL;
BEGIN (* WriteCharacterString *)
   WriteNBytes(OutputFile,ADR(S), HIGH(S)+1, Dummy);
END WriteCharacterString;


PROCEDURE TakeTheseActions(I:TableIndexType);
VAR
   Tmp:ActionPointer;
   Ix:CARDINAL;

BEGIN (* TakeTheseActions *)
   Tmp := Table[I].ActionList;
   WHILE (Tmp # NIL) DO
      Ix := 0;

      IF NoExecute THEN
         WriteChar(OutputFile,'E');
         WriteChar(OutputFile,'C');
         WriteChar(OutputFile,'H');
         WriteChar(OutputFile,'O');
         WriteChar(OutputFile,' ');
      END; (* IF *)

      WHILE Tmp^.Action[Ix] # 0C DO
         WriteChar(OutputFile,Tmp^.Action[Ix]);
         INC(Ix);
      END; (* WHILE *)
      WriteChar(OutputFile,ASCII.EOL);
      Tmp := Tmp^.Next;
      WriteCharacterString('IF NOT ERRORLEVEL 0 GOTO ERROR');
      WriteChar(OutputFile,ASCII.EOL);
   END; (* WHILE *)
END TakeTheseActions;


PROCEDURE Initialize():BOOLEAN;
VAR
   Year:CARDINAL;
   MonthDay:CARDINAL;
   HourMinute:CARDINAL;
   SecondsMilliseconds:CARDINAL;
   Ch:CHAR;
   T:Options.Termination;
   R:Options.NamePartSet;
   S:StringType;
   Len:CARDINAL;
BEGIN (* Initialize *)
   WriteString('Make begins ...');
   WriteLn;
   FreeIndex := 1;
   (* Initialize CurrentTimeStamp *)
   DOSCALL(2AH, Year, MonthDay);
   DOSCALL(2CH, HourMinute, SecondsMilliseconds);
   CurrentTimeStamp.Date := ((Year - 1980) * 200H) +
                       (MonthDay DIV 100H) * 20H +
                       (MonthDay MOD 20H);
   CurrentTimeStamp.Time := (HourMinute DIV 100H) * 800H +
                       (HourMinute MOD 100H) * 20H +
                       ((SecondsMilliseconds DIV 100H) + 2) DIV 2;

   WriteString("make file name ->");
   Options.FileNameAndOptions ("makefile",MakeFileName,T,TRUE,R);
   WriteLn;

   IF (T = Options.can) OR (T = Options.esc) THEN
      RETURN(FALSE);
   END; (* IF *)

   Options.GetOption(S,Len);
   IF Len > 0 THEN
      Ch := S[0];
   ELSE
      Ch := ' ';
   END; (* IF *)
   IF CAP(Ch) = 'N' THEN
      NoExecute := TRUE;
   ELSE
      NoExecute := FALSE;
   END; (* IF *)

   Lookup(OutputFile,'\SYS\$MAKE.BAT',TRUE);
   RETURN(TRUE);
END Initialize;



PROCEDURE Terminate;
BEGIN (* Terminate *)
   WriteCharacterString('GOTO EXIT');
   WriteChar(OutputFile,ASCII.EOL);
   WriteCharacterString(':ERROR');
   WriteChar(OutputFile,ASCII.EOL);
   WriteCharacterString('PAUSE Terminated due to non-zero return code');
   WriteChar(OutputFile,ASCII.EOL);
   WriteCharacterString(':EXIT');
   WriteChar(OutputFile,ASCII.EOL);
   Close(OutputFile);
   WriteString('Make ends');
   WriteLn;
END Terminate;



BEGIN (* Make *)
   IF Initialize() THEN
      ProcessMakeFile(MakeFileName);
      Terminate;
   END; (* IF *)
END Make.