[comp.sys.atari.st] Using Micro-Emacs with TDI Modula-2/ST

manis@ubc-cs.UUCP (01/27/87)

While the TDI Modula-2/ST system is highly recommendable, I don't much
care for the editor supplied with the system.  However, the TDI editor
has one major advantage: it allows you to step through a source program,
finding compilation errors.  This article contains an interface program,
written in Modula-2, which allows you to use Micro-Emacs (v3.7i, as
posted by James Turner of Imagen Corp) in the same manner. 

Although this program can be used on any ST on which Modula-2/ST runs,
it's probably best to use a ramdisk (there is a fair amount of I/O on
startup).  I use RMD650.ACC, which provides enough room for the
compiler, linker, non-GEM library, and the compiler's intermediate
files. 

Files included
----- --------

   README.TXT    -- you are reading it.
   EDITOR.MOD    -- the interface program.
   MODEMACS.RC   -- a Micro-Emacs startup file.
   M2ERRORS.TXT  -- a prototype error message file (the full file is not
                    distributed for copyright reasons).
 
Installing the program
---------- --- -------

1) Compile and link EDITOR.MOD. If you use the OPT linker option, the program
   will take up about 8500 bytes. The default stack size of 8K is adequate.
   
2) Leaving the program name as EDITOR.PRG (so that M2DESK will know about it),
   use Show Info... to change the program type to "TOS-takes-parameters" (so
   that you can run it from the desktop). Save the desktop.
   
3) Using the supplied M2ERRORS.TXT as a format guide, type in the error
   messages from the manual. Name the resulting file M2ERRORS.TXT.
   
4) Add the contents of MODEMACS.RC to your EMACS.RC file at the end.

5) Make sure that EDITOR.PRG, EMACS.TTP, M2ERRORS.TXT, and the revised
   EMACS.RC are in the same directory, and that that directory is writable.
   
Running the program
------- --- -------

You can either open it from the desktop, or invoke it as the editor from
M2DESK.

1) If invoked with no argument, or with an argument whose name doesn't end
   in .MOD or .DEF, it pretty well does nothing except to invoke EMACS.TTP.

2) If the argument name ends in .MOD or .DEF, it searches for a file in the
   same folder named .ERM or .ERD (respectively). If no such file exists, 
   it again invokes Micro-Emacs.
   
3) If a compiler error file does exist, however, it generates a file of
   Micro-Emacs commands which annotates the source file with error numbers,
   and defines key F10 as a macro which searches for the next error. If 
   errors exist, there will be two windows in the screen: a one-line window
   containing the error message, and another window containing your source
   file.
   
Caveats
-------

1) Modula-2 code must absolutely *NEVER* contain the character sequences 
   (*% and %*) , which are used to mark error message numbers. Failure to
   abide by this restriction will cause the F10 key macro to remove a few
   characters near the offending character sequence, with little explanation.
   If this represents a problem to you, then you can change the error markers.

2) Don't do any window manipulation while using these commands. The F10 
   macro assumes there are two windows on the screen, one containing the
   source code, and one containing the error messages. Arbitrary window
   splitting or window movement will confuse it.
   
3) There have been allegations on the net that Micro-Emacs can crash because
   of the well-known problems with GEMDOS memory allocation. I don't know
   about this, as it has never happened to me. However, my programming 
   instincts suggest that if it's going to happen at all, it's most likely
   to happen with command files such as MODEMACS.RC, which contains macros
   which generate buffers of commands which are then executed. (I'm not 
   defending this style; I just couldn't think of any other way to do it.)
   Frequent saving of files might be a good idea.
   
4) As mentioned above, the directory containing the editor must be writable.
   If you aren't going to use a ramdisk, beware.
      
5) The only way I could figure out to do the error scanning was to make rather
   extensive use of the kill buffer and the mark. Sorry.
    
::::::::::::::
modemacs.rc
::::::::::::::
; Append this file to your EMACS.RC in order to handle error message scanning.

; Set up the error message window (invoked from the command file M2EMACS.ML,
; if necessary). No need to ever invoke directly.
36 store-macro
   split-current-window
   find-file "M2ERRORS.TXT"
   select-buffer "M2ERRORS.TXT"
   add-mode "view"
   1 resize-window
   next-window
[end]

; Display the error message number found in the kill buffer. Invoked by
; macro #38.
37 store-macro
   previous-window
   select-buffer "Commands"
   insert-string "select-buffer M2ERRORS.TXT"
   newline
   insert-string "beginning-of-file"
   newline
   insert-string "search-forward "
   yank
   newline
   execute-buffer "Commands"
   select-buffer "Commands"
   beginning-of-file
   set-mark
   end-of-file
   kill-region
   unmark-buffer
   select-buffer "M2ERRORS.TXT"
   next-window
[end]

; Find the next error message, and put its number in the kill buffer.
38 store-macro
   previous-window
   beginning-of-file
   next-window
   search-forward "(*%"
   3 delete-previous-character
   set-mark
   3 forward-character
   kill-region
   execute-macro-37
   3 delete-next-character
[end]
; Attach this one to key F10.
bind-to-key execute-macro-38  FND

; This file is generated by the interface program. 
execute-file "M2EMACS.ML"
::::::::::::::
m2errors.txt
::::::::::::::
--- No (further) errors.
000 Illegal character in source file.
002 Constant out of range.
003 Open comment at end of file.

    The remainder of this file is not included, both for copyright reasons
    and in order to keep the article as short as possible. The error messages
    are found on pp 141-5 of the TDI Modula/ST (2.00) manual. Be sure that
    you type them in using EXACTLY the same format as those lines above and
    below--a 3 digit error number (leading zeroes included) followed by the
    error text.

997 Type transfer function not implemented.
998 FOR limit too large.
999 Missing symbolfile(s).


::::::::::::::
editor.mod
::::::::::::::
MODULE Editor;

   (* 
    * Driver program to allow Micro-Emacs to be used with TDI Modula-2/ST.
    * This program reads the error file generated by the compiler, and 
    * generates a file of Micro-Emacs commands which annotates the program
    * file with error numbers. The present version works with Micro-Emacs
    * version 3.7i (posted to Usenet by James Turner at Imagen Corp).
    *
    * This program assumes that Micro-Emacs (EMACS.TTP) lives in the current
    * directory, and that the current directory is writable. Best bet is
    * to use a ramdisk.
    *        
    * Copyright (C) Vincent Manis, 1986. This program may be freely 
    * distributed to anyone, provided that this copyright notice 
    * appears on all copies.
    *)
     
FROM GEMX IMPORT
   BasePageAddress;
FROM GEMDOS IMPORT
   Delete, ConIn, ConOut, ConWS, Exec, ExecMode;
FROM Streams IMPORT
   StreamKinds, Stream,
   OpenStream, CloseStream, Read8Bit, Write8Bit;
FROM Strings IMPORT
   CompareResults, Assign, Concat, Compare;
FROM SYSTEM IMPORT
   BYTE, ADDRESS;
        
CONST
   EditorFile = "EMACS.TTP";
   CommandName = "M2EMACS.ML";
   CR = 15C;
   LF = 12C;
   
VAR
   CommandFile: Stream;

   (* This program is intended to be a driver for micro-Emacs. As such,
      it doesn't chat with the user. Any message which is generated is
      therefore unexpected. This procedure displays a message and waits
      for a response before proceeding. *)
   PROCEDURE Message(VAR msg: ARRAY OF CHAR);
   VAR
      ch: CHAR;
   BEGIN
      ConWS("Editor: ");
      ConWS(msg);
      ConOut(CR);
      ConOut(LF);
      ConWS("Press RETURN to continue.");
      ConOut(CR);
      ConOut(LF);
      REPEAT
         ConIn(ch);
      UNTIL ch = CR;
   END Message;
   
   (* Break up an argument into a path/file name, and extension. Return
      TRUE iff the argument was syntactically correct. *)
   PROCEDURE ParseFileName(VAR arg, FName, FExt: ARRAY OF CHAR);
   VAR
      i, j: CARDINAL;
   BEGIN
      i := 0;
      j := 0;
      WHILE (arg[i] # 0C) AND (arg[i] # '.') DO
         FName[j] := arg[i];
	 INC(i);
	 INC(j);
      END;
      FName[j] := 0C;
      j := 0;
      IF arg[i] # 0C THEN
         INC(i);
         WHILE (arg[i] # 0C) AND (j <= 3) DO
            FExt[j] := CAP(arg[i]);
            INC(i);
	    INC(j);
         END;
      END;
      FExt[j] := 0C;
   END ParseFileName;

   (* This routine generates the error message markers to be inserted
      into the file. They have to be *exactly* 3 characters long, as the
      micro-Emacs macros assume this. *)
   PROCEDURE ErrMsgText(n: INTEGER; VAR buf: ARRAY OF CHAR);
   BEGIN
      buf[0] := CHR((n DIV 100) + INTEGER('0'));
      buf[1] := CHR(((n MOD 100) DIV 10) + INTEGER('0'));
      buf[2] := CHR((n MOD 10) + INTEGER('0'));
      buf[3] := 0C;
   END ErrMsgText;
 
   (* Determine the name of the error file, return TRUE iff one *might* exist.
      (This allows this interface to be used on non-Modula files.) *)
   PROCEDURE FindErrorFile(VAR FName, FExt, ErrName: ARRAY OF CHAR): BOOLEAN;
   BEGIN
      Assign(ErrName, FName);
      IF Compare(FExt, "MOD") = Equal THEN
         Concat(ErrName, ".ERM", ErrName);
      ELSIF Compare(FExt, "DEF") = Equal THEN
         Concat(ErrName, ".ERD", ErrName);
      ELSE
         RETURN FALSE;
      END;
      RETURN TRUE;
   END FindErrorFile;

   (* Read an integer from a designated stream. *)
   PROCEDURE ReadInt(VAR s: Stream; VAR n: INTEGER);
   VAR
      ch: CHAR;
   BEGIN
      n := 0;
      REPEAT
         Read8Bit(s, ch);
      UNTIL ch > " ";
      WHILE ("0" <= ch) AND (ch <= "9") DO
         n := 10*n + (INTEGER(ch) - INTEGER("0"));
	 Read8Bit(s, ch);
      END;
   END ReadInt;

   (* Write an integer (if >= 0) and string to the command file. *)
   PROCEDURE WriteCommand(n: INTEGER; VAR str: ARRAY OF CHAR);
      PROCEDURE WriteInt(n: INTEGER);
      BEGIN
         IF n > 0 THEN
	    WriteInt(n DIV 10);
	    Write8Bit(CommandFile, CHR(n MOD 10 + INTEGER("0")));
	 END;
      END WriteInt;
   VAR
      i: INTEGER;
      k: INTEGER;
   BEGIN
      k := HIGH(str);
      IF n > 0 THEN
         WriteInt(n);
      ELSIF n = 0 THEN
         Write8Bit(CommandFile, "0");
      END;
      Write8Bit(CommandFile, " ");
      i := 0;
      WHILE (i <= k) AND (str[i] # 0C) DO
         Write8Bit(CommandFile, str[i]);
	 INC(i);
      END;
      Write8Bit(CommandFile, CR);
      Write8Bit(CommandFile, LF);
   END WriteCommand;
   
   (* This function generates a micro-Emacs command file which loads in
      a Modula-2 source file and annotates it with error messages. *)
   PROCEDURE MakeCommandFile(VAR FName, FExt, ErrName: ARRAY OF CHAR);
   VAR
      NErrs: INTEGER;
      LNum, CNum, ENum: INTEGER;
      fwd, i: INTEGER;
      Buf: ARRAY [0..100] OF CHAR;
      Emsg: ARRAY [0..10] OF CHAR;
      LastL, LastC: INTEGER;
      ErrFile: Stream;
      Result: INTEGER;
   BEGIN
      LastL := 1;
      LastC := -1;
      OpenStream(ErrFile, ErrName, READ, Result);
      IF Result = 0 THEN
         ReadInt(ErrFile, NErrs);
         IF NErrs > 0 THEN
            (* Load the error message file. *)
	    WriteCommand(-1, "execute-macro-36");
         END;	 
         FOR i := 1 TO NErrs DO
            ReadInt(ErrFile, LNum);
	    ReadInt(ErrFile, CNum);
	    ReadInt(ErrFile, ENum);
            IF LNum # LastL THEN
               WriteCommand(LNum, "goto-line");
               LastL := LNum;
               fwd := CNum;
            ELSE
               fwd := CNum - LastC;
            END;
            WriteCommand(fwd, "forward-character");
	    (* The following is complicated by a desire not to have
	       the error message delimiters appearing verbatim (so as
	       to allow us to edit this file! *)
	    Assign(Buf, 'insert-string "(');
	    Concat(Buf, '*%', Buf);
            ErrMsgText(ENum, Emsg);
	    Concat(Buf, Emsg, Buf);
	    Concat(Buf, '%*', Buf);
	    Concat(Buf, ')"', Buf);
            WriteCommand(-1, Buf);
            LastC := CNum;
         END;
         CloseStream(ErrFile, Result); 
	 IF (Result # 0) OR (NOT Delete(ErrName)) THEN
	    Message("Failed to close error file.");
	 END;
         WriteCommand(-1, "beginning-of-file");
         IF NErrs > 0 THEN 
            WriteCommand(-1, "execute-macro-38");
         END;
      END;
      WriteCommand(-1, "clear-message-line");
   END MakeCommandFile;

   (* Get the first argument from the command line. This is a bit gruesome,
      as TDI's GEMX Definition module is wrong. Actually, the parameter list
      is at offset 128 into the base page, and consists of a byte with the
      length, followed by the text, translated into uc for no particular 
      reason. *)
   PROCEDURE GetArg(VAR arg: ARRAY OF CHAR);
   TYPE
      buff = POINTER TO ARRAY [0..127] OF CHAR;
   VAR
      i, j, len: CARDINAL;
      p: buff;
   BEGIN
      p := buff(ADDRESS(BasePageAddress) + 080H);
      len := CARDINAL(p^[0])+1;
      i := 0;
      j := 0;
      WHILE (p^[i] <= " ") AND (len > 0) DO
         INC(i);
	 DEC(len);
      END;
      WHILE (len > 0) AND (p^[i] # " ") AND (p^[i] # 0C) DO
	 arg[j] := p^[i];
	 INC(i);
	 INC(j);
	 DEC(len);
      END;
      arg[j] := 0C;
   END GetArg;
   
VAR
   Result: INTEGER;
   arg, FName, FExt, ErrName, Buf: ARRAY [0..100] OF CHAR;
   Dummy: ARRAY [0..5] OF CHAR;
BEGIN
   OpenStream(CommandFile, CommandName, READWRITE, Result);
   IF Result # 0 THEN
      Message("Couldn't create the command file.");
   ELSE
      GetArg(arg);
      IF arg[0] # 0C THEN
         (* If no argument, just invoke the editor normally. *)
         ParseFileName(arg, FName, FExt);
         Assign(Buf, 'find-file "');
         Concat(Buf, FName, Buf);
	 Dummy[0] := "."; Dummy[1] := 0C;
         Concat(Buf, Dummy, Buf);
         Concat(Buf, FExt, Buf);
         Dummy[0] := '"';
         Concat(Buf, Dummy, Buf);
         WriteCommand(-1, Buf);
         IF FindErrorFile(FName, FExt, ErrName) THEN
            MakeCommandFile(FName, FExt, ErrName);
         END;
         CloseStream(CommandFile, Result);
         IF Result # 0 THEN
            Message("Failed to close the command file.");
         END;
      END;
      Exec(loadExecute, EditorFile, "", "", Result);
      IF Result # 0 THEN
         Message("Unable to execute Micro-Emacs.");
      END;
      IF NOT Delete(CommandName) THEN
         Message("Failed to delete command file.");
      END;
   END;
END Editor.