[comp.sys.amiga.tech] ARexx interface for mg3a

mwm@VIOLET.BERKELEY.EDU (Mike Meyer, I'll think of something yet) (05/24/89)

The following is the documentation for the ARexx interface I'm
planning on building into mg3. I'm sending this to two groups, hoping
for comments & suggestions, on the hopes of improving things.

The first group is the BITNET Rexx list - 'cause thats where the real
Rexx gurus live. For them, there's a brief introduction to emacs & mg.
The second group is comp.sys.amiga.tech, 'cause that's where I expect
to find those who'll actually wind up the thing. For them, I'd like to
point out that I haven't been able to keep up with the group, so could
they please _mail_ me their comments. Addresses are:

	mwm@berkeley.edu -or- ucbvax!mwm

Some of you may have seen earlier versions of this interface. It's
changed since then, but just slightly. This also looks more like a
user manual and less like a reference manual.

	Thanx,
	<mike

0) Description of emacs & mg

A brief diversion for those not familiar with emacs.  Emacs is command
based, with even the simplest actions having a command name that can
be invoked. For example, "forward-char" will go forward one character.
To make this a usable editor, the user is allowed to bind these
functions to nearly arbitrary keysequences. For example,
"forward-char" is usually bound to Control F. Each command, when
invoked, can be passed a numeric argument. Most commands just run
themselves the number of times indicated by the argument. Some modify
their behavior based on the argument; others ignore it.

Now, some specifics for mg. Mg supports the notion of "evaluating an
expression" (inherited from it's larger siblings). This was originally
implemented for startup-file processing, but has been coopted for
ARexx. An expression is just a single line of text. The first token on
the line is taken to be a command name, which will be invoked. The
second, if it's numeric, will become the numeric argument for the
command. The rest of the string after any numeric argument is
tokenized, and each token is made available as if it were a string the
command would normally read from the keyboard. As a special case, if
any token starts with a '"', all characters from it to the next '"'
are used in that argument, with quoting conventions inside of it to
allow insertion of '"''s and control characters into the string.


1) Invocation of Rexx from inside of mg

Rexx macros can be invoked from mg in one of two ways. The first way
is close to the "standard rexx" interface. The second provides much
more flexibility and power to the user.

The first way is the "rexx" command. This reads a single argument from
the user, and is passed to ARexx as a command. If the string is
quoted, ARexx will execute it as a "string macro", interpreting the
contents. If it's not quoted, the first token of the string is taken
to be a command name. That command is searched for, with either no
extension, or the extension "mg". If found, the file will be invoked,
with any text after the command name arranged as the argument to the
command. This should be the standard way of invoking rexx macros.

Note that having all unrecognized commands passed to rexx doesn't work
in mg, as it tries to complete command names. Doing this right would
require more code than is justified in a _micro_ emacs.  However, you
can use named kbd macros to invoke rexx macros, thus putting rexx
commands into the standard command set. In addition, you can also use
command completion on those macro names.

The second, and more powerful technic is "rexx-do-region". This passes
the contents of the region to ARexx as a string macro, invoking it as
a function. The numeric argument to rexx-do-string controls it's
behavior in two ways. If the argument doesn't exist, or is
non-negative, the result string returned by the macro is displayed in
the echo line. If the argument is negative, then the result string
from the macro is inserted into the buffer at the point. The absolute
magnitude of the argument to rexx-do-region is the number of argument
strings to be passed to the macro. The user is asked for these one at
a time, and they are then made available to the macro via the normal
argument mechanisms. Due to overloading of the argument, there is no
way to invoke a macro with no arguments and have it's returned value
inserted into the buffer.

A typical use for this would be to mark a Rexx procedure definition,
then use rexx-do-region to pass the function and any arguments to Rexx
for evaluation from inside of mg. Writing the function to disk is not
required, nor is writing a skeleton to invoke the function for
testing.

Rexx-do-buffer was planned to work similar to rexx-do-region. However
I did not feel that the extra functionality of rexx-do-buffer (almost
none) justified the overhead that would be required to implement it.


2) Invocation of mg from Rexx macros

When ARexx code is run from mg, any commands issued by the Rexx
interpreter are evaluated as expressions by mg. Thus, all commands
normally available to the user are available from Rexx code. However,
the tokenization must be kept in mind when writing Rexx macros. For
example, to search for the text string "the word", one should use:
'search "the word"', not 'search the word'.

In addition, most of the mg commands are of the form 'list-of-words',
with embedded '-''s. Since ARexx recognizes these as operators outside
of strings, most commands should be embedded in quotes of some form or
another. It's a good practice to put all commands in quotes,
regardless of whether they need it or not. In fact, This is true for
all Rexx command hosts, not just mg.

Mg commands offer only three return values:

	0 - completed normally
	1 - error, such as moving past the end of the file
	2 - failure, such as a user abort or command parse error

Macros should always abort on a return of 2. A return of 1 can be used
for flow control, or ignored.


3) Additional commands provided for Rexx

In addition to the commands normally available, ARexx support in mg
has commands added that allow the user to query the editor for data,
and modify it. All of these start with "rexx-", to avoid removing
otherwise useful names from the namespace. None of these commands may
be invoked except from a Rexx script.


rexx-insert takes a single argument, and inserts it into the current
buffer at the point. Note that that argument must be a valid argument
by mg expression evaluation criteria, not by Rexx's. So to insert the
value of the variable addstuff, one should do something like:

	'rexx-insert' '"'addstuff'"'

to be safe. To be very safe, and somewhat shorter, you should always
invoke rexx-insert as 'rexx-insert "', and end it with '"'.

Other rexx-specific functions that take a single string argument are:

rexx-display: instead of inserting it's argument into the buffer, this
command displays it for the user. 

There are also commands to get information from the user. They all
take a message like rexx-display, and display it as a prompt in the
echo area. They then read a string from the user, and return it as
their result. They are:

rexx-request gets an arbitrary string. rexx-request-buffer gets a
buffer name, with completion. rexx-request-function gets a function or
kbd macro name, with completion. rexx-request-macro gets a kbd macro
name, with completion. Aborts or errors during the reads are passed on
to the Rexx macro.

rexx-line is the only other routine that returns data via result. It
returns the contents of a single line of text from the current buffer,
sans any trailing newline. It's numeric argument is the line number to
be returned. Negative arguments count backwards from the end of the
buffer.  If it has no argument, it returns the current line. Referring
to a non-existent line is an error.

Finally, there is a group of routines that take a Rexx variable name
as an argument, use it as the stem of a compound symbol. The format
for all of them is them is the same: for n from 1 to stem.0, stem.n
contains data. With one exception, there are no more levels in the
compound variables. If for any reason all the stem variables the
command wants to set cannot be set, an error is returned. Note that
unlike XEDIT, the name of the stem is not fixed - it's the argument to
the command. So "rexx-point point1" <other commands> "rexx-point point2" 
creates two compound variables, point1 and point2, each holding data
about a point.

rexx-point: stem.1 is the line number the point is on, stem.2 is the
points offset into the line, stem.3 is the text of the line the point
is on.

rexx-mark: identical to rexx-point, except that it returns data about
the mark instead of the point. If no mark exists in the current
buffer, rexx.0 is set to 0, and an error is returned.

rexx-buffer: stem.1 is the buffer name, stem.2 is the file name
associated with the buffer, stem.3 is the number of lines in the
buffer, stem.4 is the number of characters in the buffer, stem.5 is
the line # the point is on, stem.6 is the line # the mark is on. If no
mark exists, stem.6 is not set, stem.0 will be 5, and an error will be
returned. If no file is associated with the buffer, the stem.2 will be
"", and no error will be signaled.

rexx-window: stem.1 is the height of the current window in
characters, stem.2 is it's width, stem.3 is the name of the displayed
buffer, stem.4 is the name of the file in that buffer. If there is no
buffer name (which should never happen), then stem.0 will be 2. If
there is no file, stem.0 will be 3. In either case, an error will be
returned.

rexx-region returns the text of the region. If no mark is set (hence
no region), stem.0 is set to 0, and an error is returned. The text of
each line is returned in stem.#, with the newline (unlike rexx-line).
The last line may not have a newline, if the region does not end after
a newline character.

rexx-buffer-list is the exception mentioned above. Instead of
returning data in stem.#, these contain another level of compound
variable. Stem.# contains data about a specific buffer, like so:
stem.#.NAME is the name of buffer #, stem.#.FILE is the file it's
editing, and stem.#.STATUS is either "" or "CHANGED " if it's been
changed, with "CURRENT" appended if this is the current buffer.


Finally, there are rexx-lock and rexx-unlock. They are used by ARexx
macros not started from mg to use it as an edit server. These are not
yet implemented because I'm still thinking about the edit server
interface.


4) Some examples

Here are some short examples that do little except exercise the various
commands, and show what they do.

/* Exercise the rexx-buffer command */

options failat 2

'rexx-buffer' buffer

'end-of-buffer'
'rexx-insert "We got' buffer.0 'items\^M"'
'rexx-insert "buffer' buffer.1 'is editing file' buffer.2'.\^M"'
'rexx-insert "with' buffer.3 'lines,' buffer.4 'characters.\^M"'
'rexx-insert "point is on line' buffer.5'"'
if buffer.0 > 5 then 'rexx-insert " and mark on line' buffer.6'.\^M"'
else 'rexx-insert " and no mark.\^M"'
exit 0

/* Exercise the rexx-buffer-list command */

options failat 2

'rexx-buffer-list' buffers

'rexx-insert "We got' buffers.0 'buffers\^M"'

do i = 1 to buffers.0
	'rexx-insert "' buffers.i.name 'file:' buffers.i.file buffers.i.status '\^M"'
	end
exit 0

/* exercise the rexx-line command */

options results
options failat 2

'rexx-line'
save = result
'end-of-buffer'
'rexx-insert' '"'save'"'
exit 0

/* exercise the rexx-point & rexx-mark commands */

options failat 2

'rexx-point' point
'rexx-mark' mark
'end-of-buffer'
'rexx-insert "*** point ***\^M"'
'rexx-insert' """we got" point.0 "items\^M"""
'rexx-insert' """point is on line" point.1 "like so\^M"""
'rexx-insert' '"'point.3'\^M"'
if point.2 = 1 then 'rexx-insert "|\^M"'
else 'rexx-insert' '"' || copies(" ", point.2 - 1) || '|\^M"'
if mark.0 = 0 then 'rexx-insert' """There is no mark\^M"
else do
	'rexx-insert "*** mark ***\^M"'
	'rexx-insert' """we got" mark.0 "items\^M"""
	'rexx-insert' """mark is on line" mark.1 "like so\^M"""
	'rexx-insert' '"'mark.3'\^M"'
	if mark.2 = 1 then 'rexx-insert "|\^M"'
	else 'rexx-insert' '"' || copies(" ", mark.2 - 1) || '|\^M"'
	end
exit 0

/* Exercise the rexx-region command */

options failat 2

'rexx-region' region

/* Now, change to an alternate buffer, and delete all text in it */
'switch-to-buffer-other-window region-test-output'
'end-of-buffer'
'beginning-of-buffer'
'kill-region'

/* Put the region from the other buffer into the current buffer */
'rexx-insert "We got' region.0 'lines!\^M"'

do i = 1 to region.0
	'rexx-insert "'region.i'"'
	end
exit 0

/* Exercise the rexx-region command */

options failat 2

'end-of-buffer'
'rexx-window' window
'rexx-insert "'we got" window.0 "items\^M"""
'rexx-insert "'window height is:" window.1"\^M"""
'rexx-insert "'window width is:" window.2"\^M"""
'rexx-insert "'buffer name is:" window.3"\^M"""
'rexx-insert "'buffer file is:" window.4"\^M"""
'rexx-insert "==========\^M"'
exit 0