DEFINITION MODULE Dispatch;

(*
    Title: 	A General Command Dispatcher
    LastEdit:	"Thu Oct 22 18:19:43 1987" by Tony Mann
    Author: 	jd
		Cambridge University Computer Laboratory
*)

(*  This module is a general command dispatcher which
    can be used by any Modula-2 program to execute commands from a stream.
    
    Dispatch analyses an input stream using two contexts which
    bind strings to ContextProcs. It reads from the supplied input stream, 
    unless this equals NIL. If so then it will make a single character
    bidirectional stream to the terminal which will be discarded on exit. 
    Otherwise, characters read from the input stream are reflected on
    the supplied output stream. If Dispatch can't make a single character
    stream, and the supplied output stream is NIL, then output will go to
    sysOut.
     
    The immediate context describes those commands which are not reflected, 
    and are executed as soon as they are unambiguously recognised. These are
    likely to be cursor movement commands and the like which can thus be
    mixed transparently into the input stream. Immediate commands are given
    for parameters the bidirectional filtered stream, which is the stream 
    upon which the commands are recognised.
    
    
    The delayed context describes those commands which are reflected, and
    which are only executed when a complete command line ending in NewLineCh
    has been detected.  This is the more usual style of command, and permits 
    such things as local editing. When the ContextProc associated with the 
    command is called, its input stream is the buffered line stream, from
    which the arguments may be read. The output stream is the output stream
    used by Dispatch for character reflection.

    Before parsing a command line, Dispatch filters out all immediate command
    names as they arrive and executes the corresponding commands. In what
    remains, Dispatch decrees that there shall be three components; 
    initial blanks, command name, and arguments. The command name ends as
    soon as a string in the delayed context is recognised; this string may
    contain embedded blanks. If an unexpected blank or a newline is met, 
    Dispatch will extend the command name if this can be done unambiguously,
    so that command abbreviations can be used. A bell is sent to the output
    stream at the first invalid character of a command name. The arguments
    which follow the command name and include the newline character, have no
    imposed format.
    
    A called action may have arguments following its name in the input
    stream, though this is not recommended for actions in the immediate
    context. The arguments can be read directly from the selected input 
    stream, which is a copy of the complete command line positioned 
    immediately after the last character of the command name. It is not
    possible to read beyond the NewLineCh with this stream. Seeking and
    Telling on this stream are permitted. Alternatively, the module
    "arguments" can be used instead to decode arguments in a standard way.
    
    A called ContextProc can call Dispatch recursively to begin dispatching 
    with new contexts. These can either be completely new contexts, or 
    modifications of the existing contexts, which can be found by
    FindContexts. The recursive call of Dispatch can be told to use the
    same stream by passing the result of FindRealInput as the input stream.
    (Note that the input stream parameter to the ContextProc is the stream
    on which to find the arguments, & will not work). The output stream
    should be the one passed to the ContextProc.
 *)

FROM Context IMPORT Context, ContextProc;
FROM Streams IMPORT Stream;


EXPORT QUALIFIED
    level, Quit, Describe, DescribeAll,
    ImmediateContext, DelayedContext, Dispatch, 
    FindContexts, FindRealInput, ChangePrompt;

VAR level: CARDINAL;
    (* The number of currently active calls of Dispatch. Reading this number
       can allow you to generate funny prompts. Setting it causes all 
       instances of Dispatch with higher numbers to exit immediately, a kind 
       of LongJump facility. Quit is equivalent to DEC (Level)
    *)


(* Some special ContextProcs which you can rebind if you like. These may also
   be called explicitly from within ContextProcs of your own devising
*)

PROCEDURE Quit (in, out: Stream);
    (* DEC (Level), which means return from most recent call of Dispatch *)

PROCEDURE Describe (in, out: Stream);
    (* Display all Delayed commands at the current level *)

PROCEDURE DescribeAll (in, out: Stream);
    (* Display all Delayed and Immediate commands at the current level *)


(* Two procedures to build default contexts: feel free to do it yourself *)


PROCEDURE ImmediateContext (): Context;
    (* Makes a context with a default binding:
       EndStreamCh (= ^@)  ->        Quit
    *)

PROCEDURE DelayedContext (): Context;
    (* Makes a context with some default bindings:
       quit                ->        Quit
       ?                   ->        Describe
    *)


PROCEDURE Dispatch (instream, outstream: Stream; Immediate, Delayed: Context;
    prompt: ARRAY OF CHAR);
        (* interprets commands either from a new single character stream
           to the terminal (mode SCNOECHO) if instream = NIL, or
           from the supplied input stream. Terminates when some bound
           procedure calls Quit. 
        *)


PROCEDURE FindRealInput (): Stream;
        (* a command which needs to read multiple lines of input should
           use this procedure to find the stream from which Dispatch is
           reading. It will be positioned immediately after the NewLineCh
           which ended the present command.It should not be EndRead'ed.
           It is a serious error to call this procedure if no instance of
           Dispatch is active.
         *)


PROCEDURE FindContexts (VAR immediate, delayed: Context);
        (* a PROC can want to find out the contexts being used by
           the instance of Dispatch which called it. It is a serious
           error to call this procedure when no instance of Dispatch is
           active.
        *)


PROCEDURE ChangePrompt (to: ARRAY OF CHAR);
        (* This procedure will change the prompt for the current level 
           of dispatch, with effect immediately after call. 
           It is a serious error to call this procedure if no instance of
           Dispatch is active.
        *)


END Dispatch.
