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.