[net.sources] MakeMake for Logitech Modula-2/86

dukelow@cod.UUCP (01/25/87)

MakeMake is a program written by Steve Tynor to create makefiles for
Modula-2 programs.  The following is a port of MakeMake to work in the
Logitech Modula-2/86 envirnoment. See comments in MakeMake.MOD for
differences and some observations on Modula-2 portability. In addition
to Steve's work I used the CmdEnv module written by Gregory Elder (with
a small modification to allow for expanded environment sizes) for
getting the command line arguments and environment variables. The file
MakeMake.MAK which I have included is a makefile produced by MakeMake.
You may have to change drive and path names to make it work on
your system. You will also have to compile CmdEnv separately since I
have it in a separate directory on my system.

Thanks to both Steve and Gregory for some very useful software.

                                Bob Dukelow
                                dukelow@nosc.arpa

====== MakeMake.MOD ==========================================================
MODULE MakeMake;

  (*
   * MAKEMAKE.  Create a MAKEFILE for a MODULA-2 program.
   *
   * Written by Steve Tynor, 30 September 1986.
   *            UUCP  : tynor@gitpyr
   *            USNAIL: 2550 Akers Mill Rd. T-2, Atlanta GA. 30339
   *
   * Permission is granted to distribute, copy and change this program as long
   * as this notice remains...
   *)

  (*
   * Modified for Logitech MODULA-2/86 by
   *           Robert A. Dukelow, 24 January 1986
   *           3467 Mt. Ariane Dr.
   *           San Diego, CA 92111
   *           UUCP: dukelow@nosc
   *
   * Program is set up for my working environment which has the compiler and
   * linker on drive D:. Most people will probably have to change
   * CompilerPathname and LinkerPathname.
   *
   * Primary changes:
   *   1. Changed philosophy when .DEF/.MOD files not found in local directory.
   *      Assume that we are *not* trying to maintain these files with this
   *      makefile. Include corresponding .SYM/.LNK files only in dependancies
   *      for other files and attempt to find the correct paths using the
   *      M2LNK/M2SYM environment variables.
   *   2. Uses CmdEnv written by Gregory Elder to both get the command line
   *      arguments and the environment variables since Logitech doesn't have
   *      a CommandLine module. (note that I had to Modify CmdEnv to allow for
   *      my expanded environment)
   *   3. The output file is now named after the main program module with
   *      a .MAK extension instead of MAKEFILE to allow maintaining more than
   *      one program in a directory.
   *   4. Creates an EXE file in addition to a LOD.
   *   5. Removed VAR's on parameters where I thought they were not necessary
   *      (mostly to reduce my own confusions).
   *
   *  Along the way I learned some things about Modula-2 portability:
   *   1. I discovered a couple of uninitialized variables in the original
   *      code. Maybe the TDI Modula-2 initializes memory?
   *   2. Seems that TDI allows passing a string constant as the actual
   *      parameter when parameter is declared as VAR? Don't know what it
   *      would do if you tried to change the value.
   *   3. TDI has a Strings module *very* similar to Logitech's (but not
   *      quite the same - sigh).
   *        a. Strings.Compare => Strings.CompareStr   (no big deal)
   *        b. Logitech uses +1/0/-1 vs Strings.?/Equal/Less
   *                                                   (easy to figure out)
   *        c. Strings.Pos has same name but different number of arguments
   *                                     (I guessed right the first time :-)
   *        d. Strings.Assign has the arguments reversed (ugh - debug time)
   *   4. ASCII.CR/LF/HT => ASCII.cr/lf/ht  (no real problem)
   *   5. TDI Modula apparently doesn't require EXPORTs in the DEF files.
   *  Overall - I sure would like to see more standardization.
   *
   * (see below for original authors comments)
   *)

  (*
   * MAKEMAKE.  Create a MAKEFILE for a MODULA-2 program.
   * Usage:
   *      MAKEMAKE "main-module-modulename"
   *
   *    Even though module dependencies are explicit in MODULA-2, it can still
   * be quite a chore to figure them out and create a MAKEFILE.  This program 
   * reads the text file associated with the module name given on the command 
   * line and recursively checks dependencies for all imported files.  It then
   * creates a MAKEFILE suitable for use with the Unix MAKE utility.  If a 
   * file is not found and is not a recognized library module, then the
   * MAKEFILE will include a comment:
   *     ## File NOT Found ##
   * on the right hand side of the dependency.
   *
   *
   * This program was written on the Atari ST using TDI Modula-2. 
   *
   * BUGS:
   *     the CommandLine module dies when a null command line is seen.  
   * Unfortunately, I no longer have the source to the module, so I can't
   * fix it.  This bug also seems to consider a command line consisting of
   * a single character as a reason for dieing.
   *
   * Suggestions for porting to another compiler:
   *
   *    1) module CommandLine is non-standard.  It exports 2 procedures:
   *          PROCEDURE GetArgument (    num      : CARDINAL; 
   *                                 VAR filename : ARRAY OF CHAR);
   *          PROCEDURE NumberOfArguments() : CARDINAL;
   *       they do just what their names imply. (ie. like C's argv, argc)
   *
   *    2) the IMPLEMENTATION MODULE MakeLibraries will have to be edited to
   *       reflect the standard library that your compiler knows about.  List
   *       any modules that you don't want included in your MAKEFILE.  Library
   *       modules are listed in the MAKEFILE, but are commented out.
   *
   *    3) the procedure ModulenameToFilename in MakeParse will have to be
   *       modified so that it creates the correct filename for your machine.
   *
   *    4) if your MAKE program has default MODULA-2 rules, set the constant
   *       MAKEHasModulaRules (in this module) to TRUE.  This will prevent
   *       MAKEMAKE from producing remake lines. 
   *    
   *    5) the procedure WriteFilename takes a parameter: extension.  Trace
   *       all the calls to this function and make sure the proper extentions
   *       are used.  in TDI Modula, '.DEF' = definition module text
   *                                 '.MOD' = implementation module text
   *                                 '.SYM' = symbol file (compiled DEF)
   *                                 '.LNK' = symbol file (compiled MOD)
   *)

FROM SYSTEM IMPORT BYTE;
IMPORT Debug;
IMPORT Break;
IMPORT InOut;
IMPORT CmdEnv;
IMPORT MakeForest;
IMPORT MakeParse;
IMPORT ASCII;
IMPORT FileSystem;
IMPORT Strings;

CONST
  MAKEHasModulaRules = FALSE;
  CompilerPathname   = 'm2 d:comp ';
  CompilerOptions    = '/b';
  LinkerPathname     = 'm2 d:link ';

VAR
  outputStream : FileSystem.File;
  column: CARDINAL; (* Output file column *)
  indent: CARDINAL; (* amount to indent when extending lines *)

  (*----------------------------------------------------------------------*)
  PROCEDURE CloseOutput;
  VAR
    reply : INTEGER;
  BEGIN
    FileSystem.Close(outputStream);
  END CloseOutput;

  (*----------------------------------------------------------------------*)
  PROCEDURE OpenOutput(): BOOLEAN;
  VAR
    filename: ARRAY [0..11] OF CHAR;
    ColPeriod: CARDINAL;
  BEGIN
    (* create a "makefile" with a name of the form mainmodule.MAK *)
    ColPeriod := Strings.Pos('.', MakeForest.FileForestEnd^.filename);
    Strings.Copy(MakeForest.FileForestEnd^.filename, 0, ColPeriod, filename);
    Strings.Concat(filename, '.MAK', filename);

    (* delete any existing file since FileSystem won't truncate old file *)
    FileSystem.Delete(filename, outputStream);

    (* create new output file *)
    FileSystem.Lookup(outputStream, filename, TRUE);

    column := 0;
    RETURN outputStream.res = FileSystem.done;
  END OpenOutput;

  (*----------------------------------------------------------------------*)
  PROCEDURE Write (ch : CHAR);
  BEGIN
    FileSystem.WriteByte (outputStream, BYTE(ch));
    INC(column);
  END Write;


  (*----------------------------------------------------------------------*)
  PROCEDURE WriteString (str : ARRAY OF CHAR);
  VAR
    c : CARDINAL;
  BEGIN
    c := 0;
    WHILE (c <= HIGH(str)) AND (str[c] <> 0C) DO
      Write(str[c]);
      INC(c);
    END; (* WHILE *)
  END WriteString;


  (*----------------------------------------------------------------------*)
  PROCEDURE WriteLn;
  BEGIN
    Write ( ASCII.cr );
    Write ( ASCII.lf );
    column := 0;
  END WriteLn;


  (*----------------------------------------------------------------------*)
  PROCEDURE SetIndent(i: CARDINAL);
  BEGIN
    indent := i;
  END SetIndent;


  (*----------------------------------------------------------------------*)
  PROCEDURE DoIndent;
  VAR
    i: CARDINAL;
  BEGIN
    FOR i := 1 TO indent DO Write(' ') END;
  END DoIndent;


  (*----------------------------------------------------------------------*)
  PROCEDURE WriteMakefile;

    (*--------------------------------------------------------------------*)
     PROCEDURE WriteFileDependency (file : MakeForest.FileDefinitionList;
                                    lhsExtention : ARRAY OF CHAR);
     VAR
       iptr : MakeForest.ImportList;
       TmpStr: ARRAY[0..100] OF CHAR;
     BEGIN
       IF file <> NIL THEN
         IF file^.filename[0] = '#' THEN
           RETURN;
         END; (* IF *)
         IF (NOT file^.local) OR (file^.path[0] <> 0C) OR file^.library THEN
           HALT; (* double check for debugging *)
         END;
         WriteLn;
         WriteFilename (file^.filename, lhsExtention);
         WriteString (': ');
         SetIndent(Strings.Length(file^.filename) + 1);
         WriteString (file^.filename);
           (* if this is a .LNK file, then don't forget the .SYM file: *)
           (* but don't do it for the main module! *)
         IF (file <> MakeForest.FileForestEnd) AND
            (Strings.CompareStr (lhsExtention, '.LNK') = 0 (* Strings.Equal *)) THEN
           Write (' ');
           WriteFilename (file^.filename, '.SYM');
         END; (* IF *)
         iptr := file^.imports;
         WHILE iptr <> NIL DO
           IF NOT iptr^.file^.library THEN
             MakeFullFilename(iptr^.file^.path, iptr^.file^.filename, '.SYM',
                              TmpStr);
             IF (column + Strings.Length(TmpStr) + 3) > 79 THEN
               WriteString (' \');
               WriteLn;
               DoIndent;
             END; (* IF *)
             Write (' ');
             WriteString(TmpStr);
           END; (* IF *)
           iptr := iptr^.next;
         END; (* WHILE *)

         iptr := file^.imports;
         column := 1000;  (* <-- force a WriteLn *)
         WHILE iptr <> NIL DO
           IF iptr^.file^.library THEN
             MakeFullFilename(iptr^.file^.path, iptr^.file^.filename, '.SYM',
                              TmpStr);
             IF (column + Strings.Length(TmpStr) + 1) > 79 THEN
               WriteLn;
               WriteString ('#libs:  ');
             END; (* IF *)
             Write (' ');
             WriteString(TmpStr);
           END; (* IF *)
           iptr := iptr^.next;
         END; (* WHILE *)

         WriteLn;
         IF NOT MAKEHasModulaRules THEN
           Write (ASCII.ht);
           WriteString (CompilerPathname);
           WriteString (file^.filename);
           WriteString (CompilerOptions);
           WriteLn;
         END; (* IF *)
       END; (* IF *)
     END WriteFileDependency;

    (*--------------------------------------------------------------------*)
    PROCEDURE WriteAllLnkFiles;
    VAR
      ptr : MakeForest.FileDefinitionList;
      TmpStr: ARRAY[0..100] OF CHAR;
    BEGIN
      ptr := MakeForest.FileForest;
      REPEAT
        IF Strings.Pos('.MOD', ptr^.filename) <= HIGH(ptr^.filename) THEN
          IF NOT ptr^.library THEN
            MakeFullFilename(ptr^.path, ptr^.filename, '.LNK', TmpStr);
            IF (column + Strings.Length(TmpStr) + 3) > 79 THEN
              WriteString (' \');
              WriteLn;
              DoIndent;
            END; (* IF *)
            Write (' ');
            WriteString(TmpStr);
          END; (* IF *)
        END; (* IF *)
        ptr := ptr^.next;
      UNTIL ptr = NIL;
      WriteLn;
    END WriteAllLnkFiles;

    (*--------------------------------------------------------------------*)
    PROCEDURE MakeFullFilename (path, filename, extention : ARRAY OF CHAR;
                                VAR fullname: ARRAY OF CHAR);
    VAR
      c : CARDINAL;
    BEGIN
      c := 0;
      REPEAT
        fullname[c] := filename[c];
        INC(c);
      UNTIL (c > HIGH (filename)) OR (filename[c] = '.') OR (filename[c] = 0C);
      fullname[c] := 0C;
      Strings.Concat(path, fullname, fullname);
      Strings.Concat(fullname, extention, fullname);
    END MakeFullFilename;

    (*--------------------------------------------------------------------*)
    PROCEDURE WriteFilename (filename, extention : ARRAY OF CHAR);
    VAR
      c : CARDINAL;
    BEGIN
      c := 0;
      REPEAT
        Write (filename[c]);
        INC(c);
      UNTIL (c > HIGH (filename)) OR (filename[c] = '.') OR (filename[c] = 0C);
      WriteString (extention);
    END WriteFilename;

    (*--------------------------------------------------------------------*)
    PROCEDURE WriteLODDependency (file : MakeForest.FileDefinitionList);
    BEGIN

      IF (NOT file^.local) OR (file^.path[0] <> 0C) OR file^.library THEN
        HALT; (* double check for debugging *)
      END;

      (* this is special stuff that didn't seem worth generalizing *)
      WriteLn;
      WriteString('# The following converts to EXE file');
      WriteLn;
      WriteFilename (file^.filename, '.EXE: ');
      WriteFilename (file^.filename, '.LOD');
      WriteLn;
      Write(ASCII.ht);
      WriteString('m2 d:lod2exe ');
      WriteFilename (file^.filename, '.LOD');
      WriteLn;

      WriteLn;
      WriteFilename (file^.filename, '.LOD');
      WriteString (':');
      SetIndent(Strings.Length(file^.filename) + 1);
      WriteAllLnkFiles;
      IF NOT MAKEHasModulaRules THEN
        Write (ASCII.ht);  WriteString (LinkerPathname);
        WriteFilename (file^.filename, '.LNK');
        WriteLn;
      END; (* IF *)
    END WriteLODDependency;

  VAR
    ptr    : MakeForest.FileDefinitionList;
(*  c      : CARDINAL; *)
  BEGIN
    IF OpenOutput() THEN
        (* print out the .LOD dependency first.... *)
      WriteLODDependency (MakeForest.FileForestEnd);
      WriteFileDependency (MakeForest.FileForestEnd, '.LNK');
      ptr := MakeForest.FileForest;
      WHILE ptr <> MakeForest.FileForestEnd DO
        IF (NOT ptr^.library) AND ptr^.local THEN
          IF Strings.Pos ('.MOD', ptr^.filename) <= HIGH(ptr^.filename) THEN
            WriteFileDependency (ptr, '.LNK');
          ELSE
            WriteFileDependency (ptr, '.SYM');
          END; (* IF *)
        END; (* IF *)
        ptr     := ptr^.next;
      END; (* WHILE *)
    ELSE
      InOut.WriteString ('Error opening MAKEFILE');
      InOut.WriteLn;
    END; (* IF *)
    CloseOutput;
  END WriteMakefile;

VAR
  modulename,
  filename : ARRAY [0 .. 132] OF CHAR;

BEGIN
  IF CmdEnv.ArgC < 1 THEN  
    InOut.WriteString ("Usage:   MAKEMAKE main-module-modulename");
    InOut.WriteLn;
  ELSE
    Strings.Assign(CmdEnv.ArgV[1]^, modulename);
    MakeParse.ParseModule (modulename, MakeParse.main, filename);
    WriteMakefile;
  END; (* IF *)
END MakeMake.
====== MakeFore.DEF ==========================================================
DEFINITION MODULE MakeForest;

  (*
   * MAKEMAKE.  Create a MAKEFILE for a MODULA-2 program.
   *
   * Written by Steve Tynor, 30 September 1986.
   *            UUCP  : tynor@gitpyr
   *            USNAIL: 2550 Akers Mill Rd. T-2, Atlanta GA. 30339
   *
   * Permission is granted to distribute, copy and change this program as long
   * as this notice remains...
   *)

  (*
   * Modified by R.A. Dukelow (24 January 1987) for to run under Logitech
   * Modula-2/86. See MakeMake.MOD for additional comments.
   *)

EXPORT QUALIFIED FileDefinitionList, ImportList, SearchPathList,
                 FileForestEnd, FileForest, LnkSearchPaths, SymSearchPaths,
                 AddImport, AddFile;

TYPE
  String = ARRAY [0 .. 100] OF CHAR;

  FileDefinitionList = POINTER TO FileDefinitionRec;
  ImportList         = POINTER TO ImportRec;
  SearchPathList     = POINTER TO SearchPathRec;

  FileDefinitionRec  = RECORD
    filename : String;
    local    : BOOLEAN;           (* file in local directory *)
    path     : String;            (* path to find .LNK/.SYM file if this is
                                     a .MOD/.DEF file *)
    library  : BOOLEAN;           (* this file is a library module *)
    next     : FileDefinitionList;
    imports  : ImportList;        (* points to linked list of imported files *)
  END; (* RECORD *)

  ImportRec  = RECORD
    file : FileDefinitionList;
    next : ImportList;
  END; (* RECORD *)

  SearchPathRec = RECORD
    path : String;
    next : SearchPathList;
  END; (* RECORD *)

VAR
  FileForestEnd,
  FileForest : FileDefinitionList;
  LnkSearchPaths,                 (* paths to search for .MOD files *)
  SymSearchPaths: SearchPathList; (* paths to search for .DEF files *)

  (*----------------------------------------------------------------------*)
  PROCEDURE AddImport (fileDefinition :  FileDefinitionList;
                       fname          : ARRAY OF CHAR); 

  (*----------------------------------------------------------------------*)
  PROCEDURE AddFile (fname          : ARRAY OF CHAR; 
                     VAR fileDefinition :  FileDefinitionList ) : BOOLEAN;
    (* returns TRUE if file added, FALSE if the file was already there *)

END MakeForest.
====== MakeFore.MOD ==========================================================
IMPLEMENTATION MODULE MakeForest;

  (*
   * MAKEMAKE.  Create a MAKEFILE for a MODULA-2 program.
   *
   * Written by Steve Tynor, 30 September 1986.
   *            UUCP  : tynor@gitpyr
   *            USNAIL: 2550 Akers Mill Rd. T-2, Atlanta GA. 30339
   *
   * Permission is granted to distribute, copy and change this program as long
   * as this notice remains...
   *)

  (*
   * Modified by R.A. Dukelow (24 January 1987) for to run under Logitech
   * Modula-2/86. See MakeMake.MOD for additional comments.
   *)

(*
                  ---------
 FileForst ----->| zzz.MOD |--> import list
                  ---------
                      |
                      .
                      .
                      .
                      V
                  ---------     ----------            ----------
                 | xxx.MOD |-->| import N |-->...--> | import 1 |
                  ---------     ----------            ----------
                      |
                  ---------
                 | xxx.DEF |--> import list
                  ---------
                      |
                  ----------
 FileForestEnd-->| main.MOD |--> import list
                  ----------

 Each element of an import list points back to a file descriptor (which may
 contain a ## comment ## rather than a real file name).
*)

IMPORT Strings, CmdEnv;
FROM   Storage IMPORT ALLOCATE;

  (*----------------------------------------------------------------------*)
  PROCEDURE FindFile (    fname          : ARRAY OF CHAR; 
                      VAR fileDefinition : FileDefinitionList);
  VAR
    found : BOOLEAN;
  BEGIN
    fileDefinition := FileForest;
    found := FALSE;
    WHILE (fileDefinition <> NIL) AND (NOT found) DO
      found := Strings.CompareStr (fname, fileDefinition^.filename) 
                            = 0 (* Strings.Equal *);
      IF NOT found THEN
        fileDefinition := fileDefinition^.next;
      END; (* IF *)
    END; (* WHILE *)
  END FindFile;


  (*----------------------------------------------------------------------*)
  PROCEDURE AddFile (    fname          : ARRAY OF CHAR; 
                     VAR fileDefinition : FileDefinitionList) : BOOLEAN;
  BEGIN
    FindFile (fname, fileDefinition);
    IF fileDefinition = NIL THEN
      NEW (fileDefinition);
      Strings.Assign (fname, fileDefinition^.filename);
      fileDefinition^.next    := FileForest;
      fileDefinition^.imports := NIL;

      (* until shown otherwise *)
      fileDefinition^.library := FALSE;
      fileDefinition^.local := TRUE;
      fileDefinition^.path := '';

      IF FileForest = NIL THEN
        FileForestEnd := fileDefinition;
      END; (* IF *)
      FileForest      := fileDefinition;
      RETURN TRUE;
    ELSE
      RETURN FALSE;
    END; (* IF *)
  END AddFile;


  (*----------------------------------------------------------------------*)
  PROCEDURE AddImport (fileDefinition :  FileDefinitionList;
                       fname          : ARRAY OF CHAR ); 
  VAR
    iPtr : ImportList;
  BEGIN
    NEW (iPtr);
    iPtr^.next              := fileDefinition^.imports;
    fileDefinition^.imports := iPtr;

    FindFile (fname, iPtr^.file);
    IF iPtr^.file = NIL THEN HALT END; (* file should already be exist *)
  END AddImport;

PROCEDURE InitSearchPaths(VAR SearchPaths: SearchPathList;
                          EnvName: ARRAY OF CHAR);
VAR
  str: String;
  i, j: CARDINAL;
  TmpPtr, TmpList, NewPathPtr: SearchPathList;
BEGIN
  (* build a temporary list in wrong order - reverse it later *)
  SearchPaths := NIL;
  TmpList := NIL;
  CmdEnv.GetEnv(EnvName, str);
  i := 0;
  WHILE (i <= HIGH(str)) AND ((str[i] = ' ') OR (str[i] = ';')) DO INC(i) END;
  WHILE (i <= HIGH(str)) AND (str[i] <> 0C) DO
    NEW(NewPathPtr);
    NewPathPtr^.next := TmpList;
    TmpList := NewPathPtr;
    j := 0;
    REPEAT
      NewPathPtr^.path[j] := str[i];
      INC(i);
      INC(j);
    UNTIL (i > HIGH(str)) OR (str[i] = 0C) OR (str[i] = ' ') OR (str[i] = ';');
    IF NewPathPtr^.path[j - 1] <> '\' THEN
      NewPathPtr^.path[j] := '\';
      INC(j);
    END;
    NewPathPtr^.path[j] := 0C;
    WHILE (i <= HIGH(str)) AND ((str[i] = ' ') OR (str[i] = ';')) DO
      INC(i);
    END;
  END; (* WHILE *)

  (* reverse the order of the list while attaching to SearchPaths *)
  WHILE TmpList <> NIL DO
    TmpPtr := TmpList;
    TmpList := TmpPtr^.next;
    TmpPtr^.next := SearchPaths;
    SearchPaths := TmpPtr;
  END;
END InitSearchPaths;


BEGIN
  FileForest := NIL;
  FileForestEnd := NIL;

  InitSearchPaths(LnkSearchPaths, 'M2LNK');
  InitSearchPaths(SymSearchPaths, 'M2SYM');
END MakeForest.
====== MakeLibr.DEF ==========================================================
DEFINITION MODULE MakeLibraries;

  (*
   * MAKEMAKE.  Create a MAKEFILE for a MODULA-2 program.
   *
   * Written by Steve Tynor, 30 September 1986.
   *            UUCP  : tynor@gitpyr
   *            USNAIL: 2550 Akers Mill Rd. T-2, Atlanta GA. 30339
   *
   * Permission is granted to distribute, copy and change this program as long
   * as this notice remains...
   *)

  (*
   * Modified by R.A. Dukelow (24 January 1987) for to run under Logitech
   * Modula-2/86. See MakeMake.MOD for additional comments.
   *)

EXPORT QUALIFIED IsALibraryModule;

  PROCEDURE IsALibraryModule (modulename : ARRAY OF CHAR) : BOOLEAN;

END MakeLibraries.
====== MakeLibr.MOD ==========================================================
IMPLEMENTATION MODULE MakeLibraries;

  (*
   * MAKEMAKE.  Create a MAKEFILE for a MODULA-2 program.
   *
   * Written by Steve Tynor, 30 September 1986.
   *            UUCP  : tynor@gitpyr
   *            USNAIL: 2550 Akers Mill Rd. T-2, Atlanta GA. 30339
   *
   * Permission is granted to distribute, copy and change this program as long
   * as this notice remains...
   *)

  (*
   * Modified by R.A. Dukelow (24 January 1987) for to run under Logitech
   * Modula-2/86. See MakeMake.MOD for additional comments.
   *)

 (* NOTE: this module is implementation specific.   Substitute the library
  *       modules supplied by your compiler.
  *)
IMPORT Strings;

CONST
  NumLibraryModules = 39;

VAR
  i: CARDINAL;
  LibraryModule : ARRAY [1 .. NumLibraryModules] OF
                      ARRAY [0 .. 20] OF CHAR;

  (*-----------------------------------------------------------------------*)
  PROCEDURE IsALibraryModule (modulename : ARRAY OF CHAR) : BOOLEAN;
    (* binary search... *)
  VAR
    low, mid, high : CARDINAL;
  BEGIN
    low := 1;
    high := NumLibraryModules;
    WHILE low <= high DO
      mid := low + (high - low) DIV 2;
      CASE Strings.CompareStr (modulename, LibraryModule[mid]) OF
        0 : RETURN TRUE; (* Strings.Equal *)
      | -1 : high := mid - 1; (* Strings.Less *)
      ELSE
          low := mid + 1;
      END; (* CASE *)
    END; (* WHILE *)
    RETURN FALSE;
  END IsALibraryModule;

BEGIN
   (* NOTE: we're doing a binary search, so these must be in sorted order: *)

   (* Logitech supplied libraries: *)
  i := 1;
  LibraryModule [i] := 'ASCII'; INC(i);
  LibraryModule [i] := 'Break'; INC(i);
  LibraryModule [i] := 'CardinalIO'; INC(i);
  LibraryModule [i] := 'Conversions'; INC(i);
  LibraryModule [i] := 'DOS3'; INC(i); (* ASCII DO... < De... *)
  LibraryModule [i] := 'DOS31'; INC(i);
  LibraryModule [i] := 'Debug'; INC(i);
  LibraryModule [i] := 'Decimals'; INC(i);
  LibraryModule [i] := 'Devices'; INC(i);
  LibraryModule [i] := 'Directories'; INC(i);
  LibraryModule [i] := 'DiskDirectory'; INC(i);
  LibraryModule [i] := 'DiskFiles'; INC(i);
  LibraryModule [i] := 'Display'; INC(i);
  LibraryModule [i] := 'ErrorCode'; INC(i);
  LibraryModule [i] := 'FileMessage'; INC(i);
  LibraryModule [i] := 'FileNames'; INC(i);
  LibraryModule [i] := 'FileSystem'; INC(i);
  LibraryModule [i] := 'InOut'; INC(i);
  LibraryModule [i] := 'Keyboard'; INC(i);
  LibraryModule [i] := 'LogiMouse'; INC(i);
  LibraryModule [i] := 'MathLib0'; INC(i);
  LibraryModule [i] := 'Mouse'; INC(i);
  LibraryModule [i] := 'NumberConversion'; INC(i);
  LibraryModule [i] := 'Options'; INC(i);
  LibraryModule [i] := 'Processes'; INC(i);
  LibraryModule [i] := 'ProgMessage'; INC(i);
  LibraryModule [i] := 'Program'; INC(i);
  LibraryModule [i] := 'RealConversions'; INC(i);
  LibraryModule [i] := 'RealInOut'; INC(i);
  LibraryModule [i] := 'RS232Code'; INC(i);
  LibraryModule [i] := 'RS232Int'; INC(i);
  LibraryModule [i] := 'RS232Polling'; INC(i);
    (* note : Strings.Compare thinks 'SY' < 'St'. (it just compares ASCII
     *        values...) So this is not exactly alphabetical... just sorted 
     *        as Strings.Compare would...
     *)
  LibraryModule [i] := 'SYSTEM'; INC(i);
  LibraryModule [i] := 'Storage'; INC(i);
  LibraryModule [i] := 'Strings'; INC(i);
  LibraryModule [i] := 'System'; INC(i);
  LibraryModule [i] := 'Termbase'; INC(i);
  LibraryModule [i] := 'Terminal'; INC(i);
  LibraryModule [i] := 'TimeDate'; INC(i);

END MakeLibraries.
====== MakePars.DEF ==========================================================
DEFINITION MODULE MakeParse;

  (*
   * MAKEMAKE.  Create a MAKEFILE for a MODULA-2 program.
   *
   * Written by Steve Tynor, 30 September 1986.
   *            UUCP  : tynor@gitpyr
   *            USNAIL: 2550 Akers Mill Rd. T-2, Atlanta GA. 30339
   *
   * Permission is granted to distribute, copy and change this program as long
   * as this notice remains...
   *)

  (*
   * Modified by R.A. Dukelow (24 January 1987) for to run under Logitech
   * Modula-2/86. See MakeMake.MOD for additional comments.
   *)

EXPORT QUALIFIED FileType, ParseModule;

TYPE
  FileType = (def, mod, main);

  (*----------------------------------------------------------------------*)
  PROCEDURE ParseModule (    modulename : ARRAY OF CHAR;
                             type       : FileType;
                         VAR filename   : ARRAY OF CHAR);

END MakeParse.
====== MakePars.MOD ==========================================================
IMPLEMENTATION MODULE MakeParse;

  (*
   * MAKEMAKE.  Create a MAKEFILE for a MODULA-2 program.
   *
   * Written by Steve Tynor, 30 September 1986.
   *            UUCP  : tynor@gitpyr
   *            USNAIL: 2550 Akers Mill Rd. T-2, Atlanta GA. 30339
   *
   * Permission is granted to distribute, copy and change this program as long
   * as this notice remains...
   *)

  (*
   * Modified by R.A. Dukelow (24 January 1987) for to run under Logitech
   * Modula-2/86. See MakeMake.MOD for additional comments.
   *)

IMPORT MakeToken;
IMPORT MakeForest;
IMPORT Strings;
IMPORT MakeLibraries;

  (*----------------------------------------------------------------------*)
  PROCEDURE ModulenameToFilename (    modulename : ARRAY OF CHAR;
                                      defOrMod   : FileType;
                                  VAR fname      : ARRAY OF CHAR);
  CONST
    EOS = 0C;
  VAR
    c : CARDINAL;
  BEGIN
    c := 0;
    WHILE (c < HIGH (modulename)) AND (c < 8) AND (modulename[c] <> EOS) DO
      fname[c] := modulename[c];
      INC (c);
    END; (* WHILE *)
    fname[c] := '.';
    IF defOrMod = def THEN
      fname[c+1] := 'D'; fname[c+2] := 'E'; fname[c+3] := 'F'; 
      fname[c+4] := EOS;
    ELSE
      fname[c+1] := 'M'; fname[c+2] := 'O'; fname[c+3] := 'D';
      fname[c+4] := EOS;
    END; (* IF *)
  END ModulenameToFilename;


  (*----------------------------------------------------------------------*)
  PROCEDURE TerminatingToken(token : ARRAY OF CHAR) : BOOLEAN;
  VAR
    temp : BOOLEAN;
  BEGIN
    (* had to split up due to compiler limitation... *)
    temp := (Strings.CompareStr (token, 'CONST')     = 0 (* Strings.Equal *)) OR
            (Strings.CompareStr (token, 'TYPE')      = 0 (* Strings.Equal *)) OR
            (Strings.CompareStr (token, 'VAR')       = 0 (* Strings.Equal *)) OR
            (Strings.CompareStr (token, 'MODULE')    = 0 (* Strings.Equal *));
    RETURN  temp OR
            (Strings.CompareStr (token, 'PROCEDURE') = 0 (* Strings.Equal *)) OR
            (Strings.CompareStr (token, 'BEGIN')     = 0 (* Strings.Equal *)) OR
            (Strings.CompareStr (token, 'END')       = 0 (* Strings.Equal *));
  END TerminatingToken;


  (*----------------------------------------------------------------------*)
  PROCEDURE ParseFROM (     fileDef : MakeForest.FileDefinitionList;
                        VAR eof     : BOOLEAN );
  VAR
    modulename, filename : ARRAY [0 .. 100] OF CHAR;
  BEGIN
    MakeToken.NextToken (modulename, eof);
    ParseModule (modulename, def, filename);

    (* AddImport follows ParseModule(..., def, ...) because this creates
       the needed filename (xxx.DEF) for the dependency list and creates
       a file record on the list with all necessary checking. *)
    MakeForest.AddImport (fileDef, filename);

    MakeToken.SkipTillSemicolon (eof);

    ParseModule (modulename, mod, filename);
  END ParseFROM;

  (*----------------------------------------------------------------------*)
  PROCEDURE ParseIMPORT (    fileDef : MakeForest.FileDefinitionList;
                         VAR eof     : BOOLEAN );
  VAR
    modulename, filename : ARRAY [0 .. 100] OF CHAR;
  BEGIN
    MakeToken.NextToken (modulename, eof);
    WHILE modulename[0] <> ';' DO
      ParseModule (modulename, def, filename);

    (* AddImport follows ParseModule(..., def, ...) because this creates
       the needed filename (xxx.DEF) for the dependency list and creates
       a file record on the list with all necessary checking. *)
      MakeForest.AddImport (fileDef, filename);

      ParseModule (modulename, mod, filename);
      MakeToken.NextToken (modulename, eof);
    END; (* WHILE *)
  END ParseIMPORT;

  (*----------------------------------------------------------------------*)
  PROCEDURE FindPath (VAR FileDefinition :  MakeForest.FileDefinitionList );
  (* Try to find a path to the indicated file *)
  VAR
    PartFilename, FullFilename: ARRAY[0..100] OF CHAR;
    success: BOOLEAN;
    PathPtr: MakeForest.SearchPathList;
    ColPeriod: CARDINAL;
  BEGIN
    (* strip off the extension *)
    ColPeriod := Strings.Pos('.', FileDefinition^.filename);
    Strings.Copy(FileDefinition^.filename, 0, ColPeriod, PartFilename);

    (* add extension and find appropriate search path list *)
    IF Strings.Pos('.MOD', FileDefinition^.filename) <=
       HIGH(FileDefinition^.filename) THEN
      PathPtr := MakeForest.LnkSearchPaths;
      Strings.Concat(PartFilename, '.LNK', PartFilename);
    ELSE
      PathPtr := MakeForest.SymSearchPaths;
      Strings.Concat(PartFilename, '.SYM', PartFilename);
    END;

    (* do the search *)
    success := FALSE;
    WHILE (NOT success) AND (PathPtr <> NIL) DO
      Strings.Concat(PathPtr^.path, PartFilename, FullFilename);
      MakeToken.OpenFile(FullFilename, success);
      IF NOT success THEN PathPtr := PathPtr^.next END;
      MakeToken.CloseFile;
    END;
    IF success THEN
      FileDefinition^.path := PathPtr^.path;
    ELSE
      FileDefinition^.path := '##UnknownPath##\';
    END;
  END FindPath; 

  (*----------------------------------------------------------------------*)
  PROCEDURE ParseModule (    modulename : ARRAY OF CHAR;
                             type       : FileType;
                         VAR filename   : ARRAY OF CHAR);
  VAR 
    token    : ARRAY [0 .. 132] OF CHAR;
    fileDef  : MakeForest.FileDefinitionList;
    success,
    eof      : BOOLEAN;

  BEGIN
    ModulenameToFilename (modulename, type, filename);

    (* add current filename to list if necessary *)
    IF MakeForest.AddFile (filename, fileDef) THEN
      (* this is a new file - map it out if can find it *)
      MakeToken.OpenFile (filename, success);

      IF NOT success THEN
        fileDef^.local := FALSE;
        IF MakeLibraries.IsALibraryModule (modulename) THEN
          fileDef^.library := TRUE;
        ELSE
          FindPath(fileDef);
        END; (* IF *)
      ELSE
        MakeToken.NextToken (token, eof);
        IF    (type = def) AND NOT
              (Strings.CompareStr (token, 'DEFINITION') = 0 (* Strings.Equal *)) THEN
          MakeForest.AddImport (fileDef, 
                                "## Expected a 'DEFINITION' module ##");
        ELSIF (type = mod) AND NOT
              (Strings.CompareStr (token, 'IMPLEMENTATION') = 0 (* Strings.Equal *)) THEN
          MakeForest.AddImport (fileDef, 
                                "## Expected an 'IMPLEMENTATION' module ##");
        ELSIF (type = main) AND NOT
              (Strings.CompareStr (token, 'MODULE') = 0 (* Strings.Equal *)) THEN
          MakeForest.AddImport (fileDef, 
                                "## Expected a 'MODULE' module ##");
        END; (* IF / ELSE / ENDIF *)
        MakeToken.SkipTillSemicolon (eof);
        token := ';';
        WHILE (NOT eof) AND (NOT TerminatingToken(token)) DO
          IF Strings.CompareStr (token, 'FROM') = 0 (* Strings.Equal *) THEN
            ParseFROM (fileDef, eof);
          ELSIF Strings.CompareStr (token, 'IMPORT') = 0 (* Strings.Equal *) THEN
            ParseIMPORT (fileDef, eof);
          END; (* IF *)
          MakeToken.NextToken (token, eof);
        END; (* WHILE *)
      END; (* IF file opened *)
      MakeToken.CloseFile;
    END; (* IF we need to check this one *)

  END ParseModule;

END MakeParse.
====== MakeToke.DEF ==========================================================
DEFINITION MODULE MakeToken;

  (*
   * MAKEMAKE.  Create a MAKEFILE for a MODULA-2 program.
   *
   * Written by Steve Tynor, 30 September 1986.
   *            UUCP  : tynor@gitpyr
   *            USNAIL: 2550 Akers Mill Rd. T-2, Atlanta GA. 30339
   *
   * Permission is granted to distribute, copy and change this program as long
   * as this notice remains...
   *)

  (*
   * Modified by R.A. Dukelow (24 January 1987) for to run under Logitech
   * Modula-2/86. See MakeMake.MOD for additional comments.
   *)

EXPORT QUALIFIED NextToken, SkipTillSemicolon, OpenFile, CloseFile;

  (*----------------------------------------------------------------------*)
  PROCEDURE OpenFile (    filename : ARRAY OF CHAR;
                      VAR success  : BOOLEAN);

  (*----------------------------------------------------------------------*)
  PROCEDURE CloseFile;

  (*----------------------------------------------------------------------*)
  PROCEDURE NextToken (VAR token : ARRAY OF CHAR;
                       VAR eof   : BOOLEAN);

  (*----------------------------------------------------------------------*)
  PROCEDURE SkipTillSemicolon (VAR eof   : BOOLEAN);

END MakeToken.
====== MakeToke.MOD ==========================================================
IMPLEMENTATION MODULE MakeToken;

  (*
   * MAKEMAKE.  Create a MAKEFILE for a MODULA-2 program.
   *
   * Written by Steve Tynor, 30 September 1986.
   *            UUCP  : tynor@gitpyr
   *            USNAIL: 2550 Akers Mill Rd. T-2, Atlanta GA. 30339
   *
   * Permission is granted to distribute, copy and change this program as long
   * as this notice remains...
   *)

  (*
   * Modified by R.A. Dukelow (24 January 1987) for to run under Logitech
   * Modula-2/86. See MakeMake.MOD for additional comments.
   *)

FROM SYSTEM IMPORT BYTE;
IMPORT FileSystem;

VAR 
  currentCh : CHAR;
  currentStream : FileSystem.File;
  
  (*======================================================================*)
  MODULE StreamStack;
  IMPORT FileSystem;
  EXPORT Pop, Push;

  CONST
    StackSize = 20;
  VAR
    StrStack  : ARRAY [1 .. StackSize] OF FileSystem.File;
    CharStack : ARRAY [1 .. StackSize] OF CHAR;
    StackPtr  : CARDINAL;

    (*--------------------------------------------------------------------*)
    PROCEDURE Push (str : FileSystem.File; ch : CHAR);
    BEGIN
      IF StackPtr + 1 <= StackSize THEN
        INC (StackPtr);
        StrStack [StackPtr] := str;
        CharStack[StackPtr] := ch;
      END; (* IF *)
    END Push;

    (*--------------------------------------------------------------------*)
    PROCEDURE Pop (VAR str : FileSystem.File; VAR ch : CHAR);
    BEGIN
      IF StackPtr > 0 THEN
        str := StrStack [StackPtr];
        ch  := CharStack[StackPtr];
        DEC (StackPtr);
      END; (* IF *)
    END Pop;

  BEGIN
    StackPtr := 0;
  END StreamStack;
  (*======================================================================*)
 

  (*----------------------------------------------------------------------*)
  PROCEDURE OpenFile (    filename : ARRAY OF CHAR;
                      VAR success  : BOOLEAN);
  BEGIN
    Push (currentStream, currentCh);
    (* open file for reading only *)
    FileSystem.Lookup(currentStream, filename, FALSE);
    currentCh := ' ';
    success := currentStream.res = FileSystem.done;
  END OpenFile;

  (*----------------------------------------------------------------------*)
  PROCEDURE CloseFile;
  BEGIN
    FileSystem.Close(currentStream);
    Pop (currentStream, currentCh);
  END CloseFile;


  (*----------------------------------------------------------------------*)
  PROCEDURE SeparatingChar (ch : CHAR) : BOOLEAN;
    (* we're just worried about a subset of the real separating chars... *)
  BEGIN
    RETURN NOT (((ORD(ch) >= ORD('a')) AND (ORD(ch) <= ORD('z'))) OR
                ((ORD(ch) >= ORD('A')) AND (ORD(ch) <= ORD('Z'))) OR
                ((ORD(ch) >= ORD('0')) AND (ORD(ch) <= ORD('9'))));
  END SeparatingChar;


  (*----------------------------------------------------------------------*)
  PROCEDURE NextToken (VAR token : ARRAY OF CHAR;
                       VAR eof   : BOOLEAN);
  VAR
    c : CARDINAL;
    b: BYTE;
  BEGIN
    c := 0;
    IF currentCh = ';' THEN
      token[0] := ';';
      token[1] := 0C;
      eof := currentStream.eof;
      FileSystem.ReadByte(currentStream, b);
      currentCh := CHAR(b);
      RETURN;
    END; (* IF *)
    WHILE SeparatingChar (currentCh) DO
      FileSystem.ReadByte(currentStream, b);
      currentCh := CHAR(b);
      IF currentCh = ';' THEN
        token[0] := ';';
        token[1] := 0C;
        eof := currentStream.eof;
        FileSystem.ReadByte(currentStream, b);
        currentCh := CHAR(b);
        RETURN;
      ELSIF currentCh = '(' THEN
        token[c] := currentCh;
        FileSystem.ReadByte(currentStream, b);
        currentCh := CHAR(b);
        IF currentCh = '*' THEN
          SkipComment;
        ELSE
          token[0] := '(';
          token[1] := currentCh;
          c := 2;
        END; (* IF *)
      END; (* IF *)
    END; (* WHILE *)
    REPEAT
      IF currentCh = '(' THEN
        token[c] := currentCh;
        FileSystem.ReadByte(currentStream, b);
        currentCh := CHAR(b);
        IF currentCh = '*' THEN
          IF c > 0 THEN
            token[c+1] := 0C;
            SkipComment;
            RETURN;
          ELSE
            SkipComment;
          END; (* IF *)
        END; (* IF *)
      END; (* IF *)
      token[c] := currentCh;
      INC (c);
      FileSystem.ReadByte(currentStream, b);
      currentCh := CHAR(b);
    UNTIL currentStream.eof OR 
          (c > HIGH (token)) OR 
          SeparatingChar (currentCh);
    token[c] := 0C;
    eof := currentStream.eof;
  END NextToken;


  (*----------------------------------------------------------------------*)
  PROCEDURE SkipTillSemicolon (VAR eof   : BOOLEAN);
  VAR
    ch : CHAR;
    b: BYTE;
  BEGIN
    REPEAT
      IF (NOT currentStream.eof) AND (currentCh = '(') THEN
        FileSystem.ReadByte(currentStream, b);
        currentCh := CHAR(b);
        IF (NOT currentStream.eof) AND (currentCh = '*') THEN
          SkipComment;
        END; (* IF *)
      ELSE
        FileSystem.ReadByte(currentStream, b);
        currentCh := CHAR(b);
      END; (* IF *)
    UNTIL currentStream.eof OR (currentCh = ';');
  END SkipTillSemicolon;


  (*----------------------------------------------------------------------*)
  PROCEDURE SkipComment;
  VAR
    level : CARDINAL;
    done  : BOOLEAN;
    b: BYTE;
  BEGIN
    level := 1;
    FileSystem.ReadByte(currentStream, b);
    currentCh := CHAR(b);
    done := FALSE;
    REPEAT
      IF (NOT currentStream.eof) AND (currentCh = '*') THEN
        FileSystem.ReadByte(currentStream, b);
        currentCh := CHAR(b);
        IF (NOT currentStream.eof) AND (currentCh = ')') THEN
          DEC(level);
          done := level = 0;
        END; (* IF *)
      ELSIF (NOT currentStream.eof) AND (currentCh = '(') THEN
        FileSystem.ReadByte(currentStream, b);
        currentCh := CHAR(b);
        IF (NOT currentStream.eof) AND (currentCh = '*') THEN
          INC(level);
        END; (* IF *)
      ELSE
        FileSystem.ReadByte(currentStream, b);
        currentCh := CHAR(b);
      END; (* IF *)
    UNTIL currentStream.eof OR done;
  END SkipComment;

BEGIN
  currentCh := ' ';
END MakeToken.
====== CmdEnv.DEF ============================================================
(*
 * Title:  Command Line/Environment Information (CmdEnv)
 * Author: Gregory Elder
 * Last Update (by Gregory Elder):  September 25, 1986
 * Last Update (by Bob Dukelow): January 19, 1987
 * Compiler: Logitech Modula-2/86
 * Description:
 * This module provides argc and argv features similar to C and a procedure
 * for getting the values of environment variables.  ArgC is the count of the
 * number of parameters on the command line.  ArgV is an array of pointers
 * to strings containing the various command line parameters.  GetEnv is
 * a procedure which, when given an enviroment variable, will return the
 * value the variable is set to.
 *
 *)

DEFINITION MODULE CmdEnv;

EXPORT QUALIFIED
	ArgC, ArgV, GetEnv;
CONST
	MaxArgs     = 10;		(* Max arguments on the command line *)
	ArraySize   = 200;              (* Max size for each argument *)

TYPE
	StringPTR = POINTER TO ARRAY[0..ArraySize] OF CHAR;
	Args = ARRAY[0..MaxArgs] OF StringPTR;

VAR
	ArgC : CARDINAL;
	ArgV : Args;

PROCEDURE GetEnv(EnvVar : ARRAY OF CHAR; VAR Strg : ARRAY OF CHAR);

END CmdEnv.
====== CmdEnv.MOD ============================================================
(*
 * Title:  Command Line/Environment Information
 * Author: Gregory Elder
 * Last Update (by Gregory Elder):  September 25, 1986
 * Last Update (by Bob Dukelow): January 19, 1987
 * Compiler: Logitech Modula-2/86
 * Description:
 * This module provides argc and argv features similar to C and a procedure
 * for getting the values of environment variables.  ArgC is the count of the
 * number of parameters on the command line.  ArgV is an array of pointers
 * to strings containing the various command line parameters.  GetEnv is
 * a procedure which, when given an enviroment variable, will return the
 * value the variable is set to.
 *
 *)

IMPLEMENTATION MODULE CmdEnv;

FROM SYSTEM IMPORT
	AX, BX, CX, GETREG, SETREG, SWI, RTSVECTOR, ADDRESS;
FROM Storage IMPORT
	ALLOCATE;
FROM ASCII IMPORT
	nul;
FROM Strings IMPORT
	Length, CompareStr;


CONST
	CmdTailAdr  = 80H;
	EnvOffSet   = 2CH;
        EnvSize     = 0FFFEH (* MAX(CARDINAL) - 1 *);

TYPE
	CardPTR = POINTER TO CARDINAL;
	EnvStringPTR = POINTER TO ARRAY[0..EnvSize] OF CHAR;

VAR
	PSP, AnAddress : ADDRESS;
	EnvPTR : EnvStringPTR;
	EnvAdrSegment : CardPTR;

PROCEDURE ParseCmdLine;
VAR
	CmdLength, i, j, start : CARDINAL;
	CmdLine, Sptr : StringPTR;
	CmdAdr : ADDRESS;
BEGIN
	CmdAdr := PSP;
	INC(CmdAdr, CmdTailAdr);		(* Get command tail *)
	CmdLine := CmdAdr;
	i := 0;
	CmdLength := ORD(CmdLine^[i]) - 1;
	INC(i);
	WHILE (i <= CmdLength) AND (CmdLine^[i] = ' ') DO
		INC(i);
		END;
	WHILE (i <= CmdLength) AND (CmdLine^[i] # ' ') DO
		INC(i);
		END;
	ArgV[0] := NIL;
	ArgC := 0;
	WHILE i <= CmdLength DO
		WHILE (CmdLine^[i] = ' ') AND (i <= CmdLength) DO
			INC(i);
			END;
		IF i <= CmdLength THEN
			start := i;
			WHILE (CmdLine^[i] # ' ') AND (i <= CmdLength) DO
				INC(i);
				END;
			ALLOCATE(Sptr, i - start + 2);
			j := 0;
			WHILE start <= i DO
				Sptr^[j] := CmdLine^[start];
				INC(start);
				INC(j);
				END;
			Sptr^[j] := nul;
			IF ArgC < MaxArgs THEN
				INC(ArgC);
				ArgV[ArgC] := Sptr;
				END;
			END;
		END;
	i := ArgC + 1;
	LOOP
		IF i > MaxArgs THEN EXIT END;
		ArgV[i] := NIL;
		INC(i);
		END;
END ParseCmdLine;

PROCEDURE GetEnv(EnvVar : ARRAY OF CHAR; VAR Strg : ARRAY OF CHAR);
CONST
	StringSize = 40;
VAR
	i, j, len : CARDINAL;
	Str1, Str2 : ARRAY[0..StringSize-1] OF CHAR;
BEGIN
	len := Length(EnvVar);
	FOR i := 0 TO (len - 1) DO
		IF (EnvVar[i] >= 'a') AND (EnvVar[i] <= 'z') THEN
			Str1[i] := CHR(ORD(EnvVar[i]) - ORD('a') + ORD('A'));
		ELSE
			Str1[i] := EnvVar[i];
			END;
		END;
	Str1[i + 1] := nul;
	i := 0;
	LOOP
		j := 0;
		WHILE (EnvPTR^[i] # '=') AND (EnvPTR^[i] # nul) DO
			Str2[j] := EnvPTR^[i];
			INC(i);
			INC(j);
			END;
		Str2[j] := nul;
		INC(i);
		IF CompareStr(Str1, Str2) = 0 THEN
			j := 0;
			WHILE EnvPTR^[i] # nul DO
				Strg[j] := EnvPTR^[i];
				INC(i);
				INC(j);
				END;
			Strg[j] := nul;
			EXIT;
		ELSE
			WHILE EnvPTR^[i] # nul DO
				INC(i);
				END;
			END;
		INC(i);
		IF EnvPTR^[i] = nul THEN
			Strg[0] := nul;
			EXIT;
			END;
		END;
END GetEnv;

BEGIN

(* Get PSP *)

SETREG(AX, 026H);
SWI(RTSVECTOR);
GETREG(BX, PSP.OFFSET);
GETREG(CX, PSP.SEGMENT);

ParseCmdLine;

(* Set up environment pointer *)

AnAddress := PSP;
INC(AnAddress, EnvOffSet);
EnvAdrSegment := AnAddress;
AnAddress.SEGMENT := EnvAdrSegment^;
AnAddress.OFFSET := 0;
EnvPTR := AnAddress;
END CmdEnv.
====== MakeMake.MAK ==========================================================

# The following converts to EXE file
MakeMake.EXE: MakeMake.LOD
	m2 d:lod2exe MakeMake.LOD

MakeMake.LOD: MakeLibr.LNK MakeToke.LNK MakePars.LNK MakeFore.LNK \
              c:\m2tools\CmdEnv.LNK MakeMake.LNK
	m2 d:link MakeMake.LNK

MakeMake.LNK: MakeMake.MOD MakePars.SYM MakeFore.SYM c:\m2tools\CmdEnv.SYM
#libs:   Strings.SYM FileSyst.SYM ASCII.SYM InOut.SYM Break.SYM Debug.SYM
#libs:   SYSTEM.SYM
	m2 d:comp MakeMake.MOD/b

MakeLibr.LNK: MakeLibr.MOD MakeLibr.SYM
#libs:   Strings.SYM
	m2 d:comp MakeLibr.MOD/b

MakeLibr.SYM: MakeLibr.DEF
	m2 d:comp MakeLibr.DEF/b

MakeToke.LNK: MakeToke.MOD MakeToke.SYM
#libs:   FileSyst.SYM SYSTEM.SYM
	m2 d:comp MakeToke.MOD/b

MakeToke.SYM: MakeToke.DEF
	m2 d:comp MakeToke.DEF/b

MakePars.LNK: MakePars.MOD MakePars.SYM MakeLibr.SYM MakeFore.SYM \
              MakeToke.SYM
#libs:   Strings.SYM
	m2 d:comp MakePars.MOD/b

MakePars.SYM: MakePars.DEF
	m2 d:comp MakePars.DEF/b

MakeFore.LNK: MakeFore.MOD MakeFore.SYM c:\m2tools\CmdEnv.SYM
#libs:   Storage.SYM Strings.SYM
	m2 d:comp MakeFore.MOD/b

MakeFore.SYM: MakeFore.DEF
	m2 d:comp MakeFore.DEF/b
====== END ===================================================================