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.