7.4. Strategies for implementing stacks

As mentioned, stacks are rarely a part of the operating system.
Therefore, under most operating systems, REXX interpreters have to
implement their own stacks. There are several strategies for doing
this, some which are listed below.

[In the operating system.]
     This is of course “the right way” to do it. However, it
     requires that the definition of the operating system is such
     that stacks are supported. Currently, only IBM mainframe-
     based systems support stack, together with a few other
     systems that have included stacks as a consequence of making
     REXX a main scripting language (Amiga and OS/2 come to mind).

[As a device driver.]
     This is really just a variation of making the stack a part of
     the operating system. However, in some systems, drivers can
     be added very easily to the system. Drivers are often
     filesystem-based, in which case driver-based stack operations
     must operate on a file or pseudo-file. But for some systems,
     adding a driver requires much more profound changes,
     reconfiguration, and often system privileges. In all cases,
     drivers are likely to be very system specific.

[As a daemon.]
     A “daemon” is background process that does some housekeeping
     service, e.g. handling mail from remote systems. Implementing
     a stack as a daemon is only slightly simpler than using a
     driver, but the main idea is the same for both approaches.

[In the interpreter.]
     Using this approach, the stack is built into the interpreter
     as a sort of extension. This is often the simplest way, since
     it require very little coordination with other programs
     during run-time.  The main problem is that the stack becomes
     private to the interpreter, so two interpreters can not use
     the same stack; not even if they are two invocations of the
     same interpreter.

These items are listed in the order of how closely they are
coupled to the operating system: the first items are very closely,
while the last items are loosely coupled. The more closely coupled
the implementation of a stack is coupled to the operating system,
the better is the chance that several interpreters on the same
system can communicate in a compatible way, using the stack.

There is room for several hybrid solutions, based on the four
fundamental approaches. For instance, a built-in stack can also
act as a daemon.

Example: Commands takes input from the stack

In the example above, the routine that is called takes its
arguments from the stack.  Similarly, commands to an external
environment can get their arguments in the same way. Here is an
example of how to do it:

     queue ‘anonymous’               /* the username */
     queue ‘user@node’               /* the password */
     queue ‘dir’                     /* first command */
     queue ‘exit’                    /* second command */
     address command ‘FTP flipper.pvv.unit.no’

Although this is very convenient in some situations, there is also
considerable disadvantages with this method: There is no real
interactive communication between the interpreter and the command;
i.e. all input meant for the command must be set up before the
command itself is invoked. Consequently, if one of the input lines
to the command provokes an error, there is very little error
handling facility. Commonly, such an error might start a cascade
of errors, as the remaining input lines are likely to be invalid,
or even be interpreted in a context different from what they were
intended.

As with all commands involving the stack, it is important to push
or queue the correct order.

Using this technique, a program can “fool” a command to do almost
anything, by storing the correct input on the stack. However,
there is a big disadvantage: Since the stack is implementation-
dependent, it is not certain that a command will take its input
from the stack. For some systems, this is the default, while for
other systems, this is only possible through some explicit action.
Some systems might not even allow commands to take their input
from the stack at all.

Example: “Execing” commands

Many script programming languages can only execute commands while
still running, or at most start a new command immediately after
the termination (like the exec() system call in Unix).  However,
the stack can be used on some systems to set up the system to
execute one or more commands after the current script terminates.
Here is an example:

      push ‘ls’         /* finally execute ‘ls’ */
      push ‘who’        /* then execute ‘who’   */
      push ‘pwd’        /* first execute ‘pwd’  */
      exit 0

Supposing that the system reads its commands from the stack if the
stack is not empty, then this script will terminate after having
set up the stack so that the three commands pwd, who and ls will
be run in that sequence. Note the order, if QUEUE had been used,
the order would be the opposite, which is perhaps more intuitive
(assuming the topmost buffer is empty).

As with the example above, this too is only relevant for some
systems, thus is not very compatible, and you should be careful
when using it. It also suffers from the lack of interactivity,
error handling, and the importance of the order in which the
strings are pushed or queued. For all practical reasons, this is
just a special case.

Using the stack to “leave behind” command names and input only
works for systems where command interpreters and commands reads
their input from the stack. This is in general true for IBM
mainframe systems, but very few other systems.




PREV NEXT