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.