DEFINITION MODULE Context;

(*
    Title: 	A module to bind strings to values
    LastEdit:	"Fri Feb 22 15:43:18 1985"
    Author: 	jd
		Cambridge University Computer Laboratory
*)

(* This module implements the abstraction of a Context, which is a means
   of binding strings to ContextProcs. A ContextProc is a procedure
   with an input stream & an output stream for parameters.
   
   To allow looking a string up in a given context, the Cursor abstraction
   is provided. A Cursor may be set in a context, and Advanced and Retreated
   much in the style of Get and Backspace. A procedure Bound is provided
   to get the ContextProc bound to the string matched to a cursor position.
*)

FROM Streams IMPORT Stream;

EXPORT QUALIFIED
    Context, ContextProc, NewContext, CopyContext, DisposeContext, 
    Bind, BindChar, Unbind, UnbindChar, SameName,
    EnumerateProc, PrintName, EnumerateContext, BoundName,
    
    Cursor, NewCursor, Bound, Advance, Retreat, Suffix, Lookup, EndCh;

CONST EndCh = 0C;

TYPE
    Context;
    
    ContextProc = PROCEDURE (Stream, Stream);


PROCEDURE NewContext (VAR c: Context);
        (* makes a new context with no bindings *)

        
PROCEDURE CopyContext (from: Context; VAR to: Context);
        (* makes a direct copy of an existing context *)


PROCEDURE DisposeContext (VAR c: Context);
        (* throw it away *)

PROCEDURE Bind (c: Context; name: ARRAY OF CHAR; p: ContextProc);
        (* binds the string name to the ContextProc p in the context c. Any
           existing binding for name is superseded. If one of
           name [1..HIGH (name)] is 0C, the remaining characters in name
           are ignored. If name [0] is 0C (= EndStreamCh) then a binding
           for EndStreamCh is created. This will be the case if name = ""
        *)
        
PROCEDURE BindChar (c: Context; name: CHAR; p: ContextProc);
        (* One of those charming quirks of Modula-2 ... *)

PROCEDURE Unbind (c: Context; name: ARRAY OF CHAR);
        (* deletes the most recent binding for the name in the context,
           possibly exposing an older binding for the same name.
        *)

PROCEDURE UnbindChar (c: Context; name: CHAR);


TYPE EnumerateProc = PROCEDURE (Stream, Stream, Context);


PROCEDURE SameName (c1, c2: Context): BOOLEAN;
    (* Returns TRUE if c1 and c2 define bindings for the same string. *)



PROCEDURE EnumerateContext (in, out: Stream; c: Context; p: EnumerateProc);
    (* Enumerates all the bound strings in the context, calling p for
       each one
    *)

PROCEDURE PrintName (in, out: Stream; c: Context);
    (* Writes the bound name of the context: a candidate EnumerateProc *)

PROCEDURE BoundName (c: Context; VAR s: ARRAY OF CHAR);
    (* returns the null terminated string which is bound to the context;
       about the only useful thing you can do with the contexts you get
       back from EnumerateContext.
    *)


(*  Cursors
    Cursors can be used to look up names in contexts. In the general form
    of usage, a cursor is set to point to the start of all strings in a 
    context by NewCursor, and then Advanced character by character over the
    name to be looked up. Any failure of Advance means that the string is
    not in the context. If Advance succeeds on all characters, it may still
    match more than one name in the context, and Suffix may be called to 
    extract the remainder of the longest common substring of all the matched
    strings. For any cursor position, Bound will indicate whether the cursor
    has entirely matched a name in the context, and will return its
    bound value.
    
    For clients not wishing to play the tricky interactive games permitted
    by these procedures, the simpler Lookup is provided.
 *)

TYPE Cursor = RECORD context: Context; next: INTEGER END;


PROCEDURE NewCursor (VAR cursor: Cursor; c: Context);
        (* A new cursor is made which is positioned before the first character
           of all strings in the context
        *)


PROCEDURE Bound (cursor: Cursor; VAR p: ContextProc): BOOLEAN;
        (* TRUE if the current cursor position corresponds to a bound name
           in the context. A result of TRUE will cause value to have been
           set to the bound value of the matched string, and it is unchanged
           otherwise.
        *)


PROCEDURE Advance (VAR cursor: Cursor; char: CHAR): BOOLEAN;
        (* Rather like Rdch. Advance tries to add char to the end of the
           name being matched in the context. If the name so constructed
           does not match any name in the context, then FALSE is returned,
           otherwise the cursor is advanced one character position and 
           TRUE is returned.
         *)

PROCEDURE Retreat (VAR cursor: Cursor): BOOLEAN;
        (* Rather like UnRdch. Retreat will return TRUE and backspace
           the cursor once for each successful Advance or Suffix call
           which has been made on it. On the next call it will return
           FALSE and leave the cursor in an undefined state.
        *)


PROCEDURE Suffix (VAR cursor: Cursor): CHAR;
        (* Used to advance the cursor to the next ambiguity. If there is only
           one choice for the next character, then this is returned and the
           cursor is advanced one position. If there is more than one choice,
           or if the end of the string has been reached, EndStreamCh
           is returned. At this point, Bound will either fail or return the
           value of the now matched name.
        *)

PROCEDURE Lookup (c: Context; name: ARRAY OF CHAR; 
        abbrev: BOOLEAN; VAR p: ContextProc): BOOLEAN;
        (* A simpler packaging of the above. name (which may be null-
           terminated) is looked up in the context. If it is matched then
           the bound procedue is put in p and TRUE is returned. 
           If abbrev is TRUE, then the name will match the shortest name
           in the context of which it is a prefix, otherwise it specifies
           the complete unabbreviated name to be matched.
        *)

END Context.
