hermes.src

        Copyright (c) Kapteyn Astronomical Institute, Groningen 2012
        All Rights Reserved.

Name:         hermes.src
Creator:      terlouw
Host:         albirumi
Date:         Dec 26, 2012
Contents:     Makefile.uti nhermes.make thermes.make absname.h aiduca.h
              alloc.h altkeyboard.h altkeysock.h bintree.h calculator.h
              callback.h changecase.h cmdstack.h contextmenu.h coamgr.h
              dcdcom.h dcderrstr.h dcdfile.h dcdgds.h dcdparse.h
              dcdscan.h dcdrange.h do_request.h editfile.h editkeys.h
              execpath.h filecontents.h findstring.h help.h helpkey.h
              hermescom.h hermesipc.h keyboard.h keysym.h keytext.h
              khnodemgr.h mainloop.h minimatch.h monopoly.h nhermescom.h
              notifier.h oshell.h parmgr.h parse.h parsekeyhelp.h
              printmgr.h prompter.h put_log.h rdefaults.h readcontext.h
              search.h showexpr.h showkeys.h substi.h taskmgr.h
              taskoutput.h tekwindow.h terminal.h thermescom.h ucamgr.h
              ushell.h varmgr.h webhelp.h windows.h y.tab.h
              absname.c aiduca.c alloc.c altkeyboard.c altkeysock.c
              bintree.c calculator.c callback.c changecase.c cmdstack.c
              coamgr.c contextmenu.c do_request.c dcderrstr.c dcdfile.c
              dcdgds.c dcdmain.c dcdparse.c dcdscan.c dcdrange.c
              editfile.c editkeys.c execpath.c filecontents.c findstring.c
              getdtablesize.c help.c helpkey.c hermesipc.c keyboard.c
              keytext.c khnodemgr.c mainloop.c minimatch.c monopoly.c
              ngetarguments.c nhermes.c notifier.c oshell.c parmgr.c
              parsekeyhelp.c printmgr.c prompter.c put_log.c rdefaults.c
              readcontext.c search.c screen.c showexpr.c showkeys.c
              socketpair.c stubs.c substi.c taskmgr.c taskoutput.c
              dcdparse.y dcdscan.l tekwindow.c terminal.c thermes.c
              ucamgr.c ushell.c varmgr.c webhelp.c hermes.dc1
              nhermes.dc1 thelp.dc1 thermes.dc1 hermes.tex parse.l
              parse.c

#>            Makefile.uti
SHELL  = /bin/sh

pack::
	$$gip_sys/pack.csh hermes.src Makefile.uti \
	nhermes.make thermes.make \
	absname.h aiduca.h alloc.h altkeyboard.h \
	altkeysock.h bintree.h calculator.h callback.h changecase.h cmdstack.h \
	contextmenu.h \
	coamgr.h dcdcom.h dcderrstr.h dcdfile.h dcdgds.h dcdparse.h dcdscan.h \
	dcdrange.h do_request.h editfile.h editkeys.h \
	execpath.h filecontents.h findstring.h help.h helpkey.h hermescom.h \
	hermesipc.h keyboard.h keysym.h keytext.h khnodemgr.h mainloop.h \
	minimatch.h monopoly.h \
	nhermescom.h notifier.h oshell.h parmgr.h parse.h parsekeyhelp.h \
	printmgr.h prompter.h put_log.h \
	rdefaults.h readcontext.h search.h showexpr.h showkeys.h substi.h \
	taskmgr.h \
	taskoutput.h tekwindow.h terminal.h thermescom.h ucamgr.h ushell.h \
	varmgr.h webhelp.h windows.h y.tab.h \
	absname.c aiduca.c alloc.c altkeyboard.c altkeysock.c \
	bintree.c calculator.c callback.c changecase.c cmdstack.c coamgr.c \
	contextmenu.c do_request.c \
	dcderrstr.c dcdfile.c dcdgds.c dcdmain.c dcdparse.c dcdscan.c \
	dcdrange.c editfile.c editkeys.c execpath.c filecontents.c \
	findstring.c getdtablesize.c help.c helpkey.c hermesipc.c \
	keyboard.c keytext.c khnodemgr.c mainloop.c minimatch.c monopoly.c \
	ngetarguments.c nhermes.c notifier.c oshell.c parmgr.c parsekeyhelp.c \
	printmgr.c prompter.c \
	put_log.c rdefaults.c readcontext.c search.c \
	screen.c showexpr.c showkeys.c socketpair.c stubs.c substi.c taskmgr.c \
	taskoutput.c \
	dcdparse.y dcdscan.l \
	tekwindow.c terminal.c thermes.c ucamgr.c ushell.c varmgr.c webhelp.c \
	hermes.dc1 nhermes.dc1 thelp.dc1 \
	thermes.dc1 hermes.tex parse.l parse.c

clean::
	rm -f nhermes thermes *.o


#<

#>            nhermes.make
#
# nhermes.make
#

SHELL   = /bin/sh

OBJECTS = nhermes.o mainloop.o taskmgr.o substi.o parmgr.o \
	  do_request.o hermesipc.o put_log.o getdtablesize.o alloc.o parse.o \
          execpath.o rdefaults.o changecase.o ngetarguments.o \
	  bintree.o dcdrange.o filecontents.o readcontext.o taskoutput.o \
          dcderrstr.o dcdfile.o dcdgds.o dcdparse.o dcdscan.o stubs.o varmgr.o \
          callback.o absname.o notifier.o

default:: nhermes

clean::   rm -f nhermes $(OBJECTS)

# =============================================================================
#
# System-dependent rules
#
# -----------------------------------------------------------------------------

nhermes: $(OBJECTS)
	-@rm -f .DONE
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "aix" ] ; then \
	touch .DONE ;\
	echo "	$(CC_COMP) -o nhermes $(OBJECTS) $(CC_LIBS) -lbsd" ;\
	$(CC_COMP) -o nhermes $(OBJECTS) $(CC_LIBS) -lbsd ;\
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "hp9000s700" ] ; then \
	touch .DONE ;\
	echo "	$(CC_COMP) -o nhermes $(OBJECTS) -lBSD $(CC_LIBS)" ;\
	$(CC_COMP) -o nhermes $(OBJECTS) -lBSD $(CC_LIBS) ;\
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "hp9000s300" ] ; then \
	touch .DONE ;\
	echo "	$(CC_COMP) -o nhermes $(OBJECTS) -lBSD $(CC_LIBS)" ;\
	$(CC_COMP) -o nhermes $(OBJECTS) -lBSD $(CC_LIBS) ;\
	fi
	-@if [ ! -r .DONE ] ; then \
	touch .DONE ;\
	echo "	$(CC_COMP) -o nhermes $(OBJECTS) $(CC_LIBS)" ;\
	$(CC_COMP) -o nhermes $(OBJECTS) $(CC_LIBS) ;\
	fi
	-@rm -f .DONE

dcdparse.c : dcdparse.y
	-@if [ "$(ARCHITECTURE)" = "hp9000s700" ] ; then \
	echo "	yacc -d dcdparse.y; mv -f y.tab.c dcdparse.c" ;\
	yacc -d dcdparse.y; mv -f y.tab.c dcdparse.c ;\
	else \
	echo "	touch dcdparse.c" ;\
	touch dcdparse.c ;\
	fi

dcdscan.c : dcdscan.l
	-@if [ "$(ARCHITECTURE)" = "hp9000s700" ] ; then \
	echo "	lex -t dcdscan.l | sed -e's/yy/dcdyy/g' -e's/stdin/NULL/g' -e's/stdout/NULL/g'> dcdscan.c" ;\
	lex -t dcdscan.l | sed -e's/yy/dcdyy/g' -e's/stdin/NULL/g' -e's/stdout/NULL/g' > dcdscan.c ;\
	else \
	echo "	touch dcdscan.c" ;\
	touch dcdscan.c ;\
	fi

parse.c : parse.l
	-@if [ "$(ARCHITECTURE)" = "linux" ] ; then \
	echo "  touch parse.c" ;\
	else \
	echo "  lex -t parse.l | sed -e's/stdin/NULL/g' -e's/stdout/NULL/g' > parse.c" ;\
	lex -t parse.l | sed -e's/stdin/NULL/g' -e's/stdout/NULL/g' >parse.c ;\
	fi

# -----------------------------------------------------------------------------

nhermes.o : nhermes.c hermescom.h nhermescom.h

ngetarguments.o : ngetarguments.c nhermescom.h

do_request.o : do_request.c hermescom.h

hermesipc.o : hermesipc.c hermescom.h

put_log.o : put_log.c hermescom.h

substi.o : substi.c hermescom.h

taskmgr.o : taskmgr.c hermescom.h

parmgr.o : parmgr.c hermescom.h nhermescom.h

bintree.o: bintree.c bintree.h hermescom.h

.c.o :
	$(CC_COMP) -c $(CC_OPTS) $<
#<

#>            thermes.make
#
# thermes.make
#

SHELL   = /bin/sh

OBJECTS = thermes.o mainloop.o keyboard.o screen.o taskmgr.o substi.o \
          parmgr.o prompter.o coamgr.o do_request.o getdtablesize.o \
          hermesipc.o put_log.o alloc.o parse.o altkeysock.o altkeyboard.o \
          execpath.o rdefaults.o changecase.o ushell.o bintree.o \
          contextmenu.o showkeys.o dcdrange.o printmgr.o \
          filecontents.o readcontext.o ucamgr.o help.o oshell.o findstring.o \
          tekwindow.o terminal.o minimatch.o aiduca.o monopoly.o editkeys.o \
          cmdstack.o editfile.o search.o helpkey.o khnodemgr.o keytext.o \
          parsekeyhelp.o taskoutput.o \
          dcderrstr.o dcdfile.o dcdgds.o dcdparse.o dcdscan.o stubs.o varmgr.o \
          showexpr.o calculator.o callback.o absname.o webhelp.o notifier.o

default:: thermes

clean::   rm -f thermes $(OBJECTS)

# =============================================================================
#
# System-dependent rules
#
# -----------------------------------------------------------------------------

thermes: $(OBJECTS)
	-@rm -f .DONE
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "aix" ] ; then \
	touch .DONE ;\
	echo "	$(CC_COMP) -o thermes $(OBJECTS) -lcurses $(CC_LIBS) -lbsd" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -lcurses $(CC_LIBS) -lbsd ;\
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "hp9000s700" ] ; then \
	case `uname -r` in \
	*.0?.*) \
		touch .DONE ;\
		echo "	$(CC_COMP) -o thermes $(OBJECTS) -lcurses  -ltermlib -lBSD $(CC_LIBS)" ;\
		$(CC_COMP) -o thermes $(OBJECTS) -lcurses  -ltermlib -lBSD $(CC_LIBS) ;\
		;;\
	*) \
		touch .DONE ;\
		echo "	$(CC_COMP) -o thermes $(OBJECTS) -ltermcap -lBSD $(CC_LIBS)" ;\
		$(CC_COMP) -o thermes $(OBJECTS) -ltermcap -lBSD $(CC_LIBS) ;\
		;;\
	esac ;\
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "hp9000s300" ] ; then \
	touch .DONE ;\
	echo "	$(CC_COMP) -o thermes $(OBJECTS) -lcurses  -ltermlib -lBSD $(CC_LIBS)" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -lcurses  -ltermlib -lBSD $(CC_LIBS) ;\
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "linux" ] ; then \
	touch .DONE ;\
	if [ ! -r /usr/lib/libbsd.a ] ; then \
	echo "  $(CC_COMP) -o thermes $(OBJECTS) -lncurses $(CC_LIBS)" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -lncurses $(CC_LIBS) ;\
	else \
	echo "  $(CC_COMP) -o thermes $(OBJECTS) -lncurses -lbsd $(CC_LIBS)" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -lncurses -lbsd $(CC_LIBS) ;\
	fi ; \
	if [ ! -x thermes ] ; then \
	echo "  $(CC_COMP) -o thermes $(OBJECTS) -lcurses  -ltermcap -lbsd $(CC_LIBS)" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -lcurses  -ltermcap -lbsd $(CC_LIBS) ;\
	fi ; \
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "linux64" ] ; then \
	touch .DONE ;\
	if [ ! -r /usr/lib/libbsd.a ] ; then \
	echo "  $(CC_COMP) -o thermes $(OBJECTS) -lncurses $(CC_LIBS)" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -lncurses $(CC_LIBS) ;\
	else \
	echo "  $(CC_COMP) -o thermes $(OBJECTS) -lncurses -lbsd $(CC_LIBS)" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -lncurses -lbsd $(CC_LIBS) ;\
	fi ; \
	if [ ! -x thermes ] ; then \
	echo "  $(CC_COMP) -o thermes $(OBJECTS) -lcurses  -ltermcap -lbsd $(CC_LIBS)" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -lcurses  -ltermcap -lbsd $(CC_LIBS) ;\
	fi ; \
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "sol4" ] ; then \
	touch .DONE ;\
	echo "	$(CC_COMP) -o thermes $(OBJECTS) -ltermlib $(CC_LIBS)" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -ltermlib $(CC_LIBS) ;\
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "i386" ] ; then \
	touch .DONE ;\
	echo "	$(CC_COMP) -o thermes $(OBJECTS) -ltermlib $(CC_LIBS)" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -ltermlib $(CC_LIBS) ;\
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "mips" -a -r "/usr/lib/libcursesX.a" ] ; then \
	touch .DONE ;\
	echo "	$(CC_COMP) -o thermes $(OBJECTS) -lcursesX $(CC_LIBS)" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -lcursesX -ltermlib $(CC_LIBS) ;\
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "apple_m" ] ; then \
        touch .DONE ;\
        echo "  $(CC_COMP) -o thermes $(OBJECTS) -lcurses  $(CC_LIBS)" ;\
        $(CC_COMP) -o thermes $(OBJECTS) -lcurses   $(CC_LIBS) ;\
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "apple_i" ] ; then \
        touch .DONE ;\
        echo "  $(CC_COMP) -o thermes $(OBJECTS) -lcurses  $(CC_LIBS)" ;\
        $(CC_COMP) -o thermes $(OBJECTS) -lcurses   $(CC_LIBS) ;\
	fi
	-@if [ ! -r .DONE -a "$(ARCHITECTURE)" = "apple_i64" ] ; then \
        touch .DONE ;\
        echo "  $(CC_COMP) -o thermes $(OBJECTS) -lcurses  $(CC_LIBS)" ;\
        $(CC_COMP) -o thermes $(OBJECTS) -lcurses   $(CC_LIBS) ;\
	fi
	-@if [ ! -r .DONE ] ; then \
	touch .DONE ;\
	echo "	$(CC_COMP) -o thermes $(OBJECTS) -lcurses  -ltermlib $(CC_LIBS)" ;\
	$(CC_COMP) -o thermes $(OBJECTS) -lcurses  -ltermlib $(CC_LIBS) ;\
	fi
	-@rm -f .DONE

terminal.o : terminal.c
	-@if [ "$(ARCHITECTURE)" = "alliant" -a "`echo $(CC_COMP) | grep gcc`" ] ; then \
	echo "	$(CC_COMP) -c -traditional $(CC_OPTS) terminal.c" ;\
	$(CC_COMP) -c -traditional $(CC_OPTS) terminal.c ;\
	elif [ "$(ARCHITECTURE)" = "sun4" -a "`echo $(CC_COMP) | grep gcc`" ] ; then \
	echo "	$(CC_COMP) -c -traditional $(CC_OPTS) terminal.c" ;\
	$(CC_COMP) -c -traditional $(CC_OPTS) terminal.c ;\
	else \
	echo "	$(CC_COMP) -c $(CC_OPTS) terminal.c" ;\
	$(CC_COMP) -c $(CC_OPTS) terminal.c ;\
	fi

dcdparse.c : dcdparse.y
	-@if [ "$(ARCHITECTURE)" = "hp9000s700" ] ; then \
	echo "	yacc -d dcdparse.y; mv -f y.tab.c dcdparse.c" ;\
	yacc -d dcdparse.y; mv -f y.tab.c dcdparse.c ;\
	else \
	echo "	touch dcdparse.c" ;\
	touch dcdparse.c ;\
	fi

dcdscan.c : dcdscan.l
	-@if [ "$(ARCHITECTURE)" = "hp9000s700" ] ; then \
	echo "	lex -t dcdscan.l | sed -e's/yy/dcdyy/g' -e's/stdin/NULL/g' -e's/stdout/NULL/g' > dcdscan.c" ;\
	lex -t dcdscan.l | sed -e's/yy/dcdyy/g' -e's/stdin/NULL/g' -e's/stdout/NULL/g' > dcdscan.c ;\
	else \
	echo "	touch dcdscan.c" ;\
	touch dcdscan.c ;\
	fi

screen.o : screen.c windows.h
	-@if [ "$(ARCHITECTURE)" = "hp9000s300" ] ; then \
	echo "	$(CC_COMP) -c $<" ;\
	$(CC_COMP) -c $< ;\
	elif [ "$(ARCHITECTURE)" = "mips" -a -r "/usr/lib/libcursesX.a" ] ; then \
	echo "	$(CC_COMP) -c -D__GIPSY_CURSESX__ $<" ;\
	$(CC_COMP) -c -D__GIPSY_CURSESX__ $< ;\
	else \
	echo "	$(CC_COMP) -c $(CC_OPTS) $<" ;\
	$(CC_COMP) -c $(CC_OPTS) $< ;\
	fi

parse.c : parse.l
	-@if [ "$(ARCHITECTURE)" = "linux" ] ; then \
	echo "	touch parse.c" ;\
	else \
	echo "	lex -t parse.l | sed -e's/stdin/NULL/g' -e's/stdout/NULL/g' > parse.c" ;\
	lex -t parse.l | sed -e's/stdin/NULL/g' -e's/stdout/NULL/g' >parse.c ;\
	fi

# -----------------------------------------------------------------------------

thermes.o : thermes.c hermescom.h thermescom.h windows.h

keyboard.o : keyboard.c

oshell.o : oshell.c

coamgr.o : coamgr.c hermescom.h thermescom.h

do_request.o : do_request.c hermescom.h

hermesipc.o : hermesipc.c hermescom.h

prompter.o : prompter.c hermescom.h thermescom.h

put_log.o : put_log.c hermescom.h

substi.o : substi.c hermescom.h

taskmgr.o : taskmgr.c hermescom.h

parmgr.o : parmgr.c hermescom.h

bintree.o : bintree.c bintree.h hermescom.h

.c.o :
	$(CC_COMP) -c $(CC_OPTS) $<
#<

#>            absname.h
#if !defined(_absname_h_)
#define _absname_h
char *absname(char *dir, char *relname);
#endif /* _absname_h_ */
#<

#>            aiduca.h
#if !defined(_aiduca_h_)
#define _aiduca_h_
extern void AidUca(void);
#endif
#<

#>            alloc.h
#if !defined(_alloc_h_)
#define _alloc_h_
extern void       *Malloc    (int size);
extern void       *Realloc   (void *orig, int newsize);
extern void       *Calloc    (int nelem, int elsize);
extern void        Free      (void* ptr);
extern char       *StrDup    (char *orig);
#define New(type) ((type *)Calloc(1,sizeof(type)))
#define NNew(n,type) ((type *)Calloc(n,sizeof(type)))
#define Delete(x)  {Free(x); x=NULL;}
#endif
#<

#>            altkeyboard.h
#if !defined(_altkeyboard_h_)
#define _altkeyboard_h_
extern void InitAltKey(void);
#endif

#<

#>            altkeysock.h
#if !defined(_altkeysock_h_)
#define _altkeysock_h_
extern int        AltKeySock (void);
extern void       RmAltKeySock(void);
extern int        AcceptAltKey(int);
#endif
#<

#>            bintree.h
/* bintree.h
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

Author: J.P. Terlouw.
*/
#if !defined(_bintree_h_)
#define _bintree_h_
#include "hermescom.h"        /* defines NAMLEN */
typedef struct _Node {
   char          name[NAMLEN];
   struct _Node *left;
   struct _Node *right;
   void         *data;
} *Node, **Tree;

typedef void(*TraverseProc)(Node,void*);

Node  RetrieveNode(Tree,char*);
Node  InsertNode(Tree,Node);
Node  UnlinkNode(Tree,char*);
void  TraverseTree(Tree,TraverseProc,void*);
#endif
#<

#>            calculator.h
#if !defined(_calculator_h_)
#define _calculator_h_
extern void calculator(void);
#endif /* _calculator_h_ */
#<

#>            callback.h
#if !defined (_callback_h_)
#define _callback_h_
typedef void* CallbackChain;
typedef void* CallbackId;
typedef void (*CallbackProc)(CallbackId,void*,void*);
CallbackChain NewCbChain(void);
void DeleteCbChain(CallbackChain chain);
CallbackId RegisterCb(CallbackChain chain, CallbackProc proc, void *cldata);
void DeregisterCb(CallbackChain chain, CallbackId id);
void InvokeCb(CallbackChain chain, void *reason);
#endif
#<

#>            changecase.h
/*
 *  changecase.c
 *
 *  Utility to change character strings to upper or lower case.
 */
#if !defined(_changecase_h_)
#define _changecase_h_
extern void StringUp(char *);
extern void StringLow(char *);
#endif
#<

#>            cmdstack.h
#if !defined(_cmdstack_h_)
#define _cmdstack_h_
#define CMD_FORWARD 1
#define CMD_BACKWARD -1
extern void StoreCmd(char*);
extern char *RecallCmd(char*,int);
extern void ResetCmd(void);
extern void InitCmd(void);
extern void FinisCmd(void);
#endif
#<

#>            contextmenu.h
/* contextmenu.h
*/
#if !defined(_contextmenu_h_)
#define _contextmenu_h_
extern void ContextMenu(TaskStatus);
#endif
#<

#>            coamgr.h
#if !defined(_coamgr_h_)
#define _coamgr_h_
extern void InitCoa(int,int);
extern void ShowPage(int,int);
extern int  SearchCoa(char*,int);
extern void PrintCoa(int,int);
extern int SetScroll(int);
#endif
#<

#>            dcdcom.h
#if !defined(_dcdcom_h_)
#define _dcdcom_h_

                             /* rename externals */
#define yylval   dcd_val
#define dcdyylval   dcd_val
#define yyparse  dcd_parse
#define yylex    dcd_lex
#define dcdyylex    dcd_lex

                             /* error codes */

#define VARCASS  -1   /* attempt to assign to constant */
#define VARLONG  -2   /* variable name too long */
#define VARFULL  -3   /* variable table full */
/*              -11     Herinp: not I, F, C, etc. */
#define BADFUN  -12  /* unknown function */
#define BADPSYN -13  /* parser syntax error */
#define BADSSYN -14  /* scanner syntax error */
#define BADINCR -15  /* bad list increment */
#define BADNARG -16  /* incorrect number of arguments */
#define ERARITH -17  /* arithmetic error */
/*              -18     insufficient internal memory */
#define BADCONV -19  /* conversion error */
#define BADLSTL -20  /* unequal list lengths */
/*              -21     empty list */ 
/*              -22     nested lists */
#define BUFOVF  -23  /* output buffer overflow */
#define FLOVUF  -24  /* floating overflow/underflow in conversion */ 
#define BADELEM -25  /* bad list element */
#define BADOPER -26  /* bad operand */
#define BADARG  -27  /* bad argument type */
#define BADBOX  -28  /* bad image box */
#define BADISUB -29  /* too many image subsets */
#define BADCTYP -39  /* bad column type */
#define TMPOVF  -40  /* temporaries table overflow */
#define BADRANG -41  /* bad rows range */  
#define BADFILE -42  /* bad file name */
#define BADCOL  -43  /* bad file column */
#define TOOMANY -44  /* too many items requested */
#define BADINDX -45  /* bad select index */
#define BADSUB  -46  /* bad subset specification */


                             /* variable types */
#define NONE         0
#define DOUBLE       1
#define LIST         2
#define TEXT         3

                             /* constants      */

#define MAXFLOAT        1.2e+37
#define MINFLOAT        0.8e-37
#define MAXSHORT        32767.5
#define MINSHORT       -32768.5
#define MAXINT     2147483647.5
#define MININT    -2147483648.5
#if 0 /* old ... */
#define MAXLOG             38.0
#define MINLOG            -38.0  
#else /* ... new */
#define MAXLOG          37.0792
#define MINLOG         -37.0969
#endif

typedef struct {
   int     type;     /* type of variable */
   int     num;      /* number of values in list */
   double  value;    /* value of double */
   double *values;   /* pointer to list values or string */
} Value;

#define  MAXARG     4
typedef struct {
   int   num;           /* number of arguments */
   Value val[MAXARG];   /* arguments */
} Arglst;

#define New(type) ((type *)calloc(1,sizeof(type)))   
#define NNew(n,type) ((type *)calloc(n,sizeof(type)))
#define Delete(x)  {free(x); x=NULL;}

#define NINT(x) (x>0.0?(int)(x+0.5):(int)(x-0.5))

#endif /* _dcdcom_h_ */
#<

#>            dcderrstr.h
#if !defined(_dcderrstr_h_)
#define _dcderrstr_h_
extern char* dcderrstr(int code);
#endif /* _dcderrstr_h_ */
#<

#>            dcdfile.h
#if !defined(_dcdfile_h_)
#define _dcdfile_h_
extern Value dcd_file(Value filename, Value column, Value rows);
#endif /* _dcdfile_h_ */
#<

#>            dcdgds.h
#if !defined(_dcdgds_h_)
#define _dcdgds_h_
extern Value dcd_descr(Value setname, Value lstring, Value keyword);
extern Value dcd_image(Value setname, Value lstring, Value boxval);
extern Value dcd_table(Value setname, Value lstring,
                       Value tabval, Value colval, Value rows);
#endif /* _dcdgds_h_ */
#<

#>            dcdparse.h
#if !defined(_dcdparse_h_)
#define _dcdparse_h_
#include "gipsyc.h"
typedef struct {
   char *expr;
   char *value;
} ExprVal;
typedef void (*ExprProc)(long, ExprVal*, void*);
extern int EvalExpr(char *expr);
extern int DecodeReal(char *expr, float *values, int number, int *error);
extern int DecodeFint(char *expr, fint *values, int number, int *error);
extern int DecodeDouble(char *expr, double *values, int number, int *error);
extern void dcderror(int code);
extern long InsertExprProc(ExprProc,void*);
extern void RemoveExprProc(long);
#endif /* _dcdparse_h_ */
#<

#>            dcdscan.h
#if !defined(_dcdscan_h_)
#define _dcdscan_h_
extern void dcdinit(char *text);
extern void dcdmode(int x);
#endif /* _dcdscan_h_ */
#<

#>            dcdrange.h
#if !defined(_dcdrange_h_)
#define _dcdrange_h_
int dcdrange(char *string, int *start, int *end);
#endif
#<

#>            do_request.h
/* do_request.h
*/

#if !defined(_do_request_h_)
#define _do_request_h_
extern void  do_request (TaskStatus);
extern bool satisfy(TaskStatus, UserinpRequest);
#endif  /* DO NOT ADD ANYTHING AFTER THIS LINE */
#<

#>            editfile.h
#if !defined(_editfile_h_)
#define _editfile_h_
#define EDT_CANCEL      -1    /* user cancelled edit */
#define EDT_STARTFAIL   -2    /* cannot start editor */
#define EDT_NOEDITOR  -666    /* implementation does not support editor */
typedef void(* EditProc)(long, char*, int, void*);
extern long InsertEditProc(EditProc,void*);
extern void RemoveEditProc(long);
extern int EditFile(char *filename);
#endif
#<

#>            editkeys.h
#if !defined(_editkeys_h_)
#define _editkeys_h_
extern int EditKeys(char *taskname);
#endif
#<

#>            execpath.h
#if !defined(_execpath_h_)
#define _execpath_h_
extern int   SetExecPath(char*);
extern char* GetExecPath();
extern char* FindExec(char*);
#endif
#<

#>            filecontents.h
#if !defined(_filecontents_h_)
#define _filecontents_h_
extern char *FileContents(char *path, int *fsize);
#endif
#<

#>            findstring.h
#if !defined(_findstring_h_)
#define _findstring_h_
char *findstring(char *block, char *start, char *string, int backw);
#endif
#<

#>            help.h
#if !defined(_help_h_)
#define _help_h_
extern void InitHelp(void);
extern void InitMosaic(void);
extern void SetKeyHelp(int mode);
#endif
#<

#>            helpkey.h
#if !defined(_helpkey_h_)
#define _helpkey_h_
extern void HelpKey(char*);
extern void StopHelpKey(void);
#endif
#<

#>            hermescom.h
/* hermescom.h
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

           'hermescom.dc3'
Include:    hermescom

Purpose:    Private. Definitions and declarations to be used in
            hermes communication routines.

File:       hermescom.h

Author:     J.P. Terlouw

Updates:    18-Oct-90  --  original document
            17-Jan-91  --  revised

*/

#if !defined(_hermescom_h_)
#define _hermescom_h_
#include "stddef.h"
#include "stdio.h"
#include "taskcom.h"
#undef   real /* interferes with Solaris <sys/t_lock.h> */

/* ========================================================================= */
/*                   Various dimensions and constants                        */
/* ------------------------------------------------------------------------- */
#define NAMLEN     31  /* servant name length */
#define SCREEN      1  /* terminal screen device mask */
#define LOGFILE     2  /* log file device mask */
#define NOEXPERT    8  /* terminal screen, suppressed in "expert mode" */
#define SCREENTEST 16  /* test mode terminal screen device mask */
#define LOCALDEV   0x80000000 /* indicates request from local_msg */
#define DEVMASK    (SCREEN|LOGFILE|NOEXPERT) /* default device mask */
#define ERRLEV      4  /* default error level */
#define MESLEV      1  /* default message level */
#define TPLEN      FILENAME_MAX  /* task path length */
#define DIRLEN     FILENAME_MAX  /* working directory length */
/*
 *   bits to be used with status mask to determine which status messages are
 *   to be logged:
 */
#define S_TERM      1  /* task termination */
#define S_PAUSE     2  /* pause */
#define S_USERINP   4  /* user input request */
#define S_STATUS    8  /* status message */

#define SW          MSGLEN+80 /* should be "ample" */


/* ========================================================================= */
/*                   keyword substitution specification                      */
/* ------------------------------------------------------------------------- */
typedef struct _KeySub {
   char key[KEYLEN+1];      /* userinp keyword */
   char equ[KEYLEN+1];      /* equivalent keyword */
   struct _KeySub *next;    /* link to next specification */
} *KeySub;

/* ========================================================================= */
/*                       process status block                                */
/* ------------------------------------------------------------------------- */
typedef struct _ProcStatus {
   struct _ProcStatus *next;     /* link to next block */
   KeySub      keysub       ;    /* pointer to keyword substitution frame */
   int         pid;              /* process identification */
   char        name[NAMLEN+1];   /* process name */
   int         fd_master;        /* descriptor from which Hermes reads */
   long        id_master;        /* callback associated with fd_master */
   int         fd_omaster;       /* descriptor to which Hermes writes  */
   int         fd_servant;/* descriptor through which servant communicates */
   int         fd_stdout;        /* servant's stdout and stderr pipe */
   long        id_stdout;        /* callback associated with fd_stdout */
   int         fd_notify;        /* descriptor for event notification */
   int         evdr_parent;      /* event-driven parent process */
} *ProcStatus;

/* ========================================================================= */
/*                          task status block                                */
/* ------------------------------------------------------------------------- */
typedef enum { IDLE,  WAITRUN, RUNNING, WAITINP, PAUSING, FINISHED,
               FATAL, ABORTED, CRASHED, STARTFAIL, WAITEDIT, NODEFAULT
             } state;     /* possible process states */

typedef struct _TaskStatus {
   struct _TaskStatus *next;     /* link to next status block */
   struct _TaskStatus *caller;   /* XEQ caller */
   struct _TaskStatus *callee;   /* XEQ callee */
   struct _TaskStatus *xcaller;  /* XEQCONT caller */
   struct _TaskStatus **xcallee; /* XEQCONT callees */
   ProcStatus  proc;             /* pointer to process status block */
   char        name[NAMLEN+1];   /* task name */
   int         errlev;           /* error level */
   int         meslev;           /* message level */
   int         unhide;           /* un-hide 'hidden' userinp requests */
   int         devmask;          /* anyout device mask */
   void       *isdata;           /* implementation specific data */
   void      (*isproc)();        /* implementation specific procedure */
   long        parcb;            /* parameter change callback id */
   long        edtcb;            /* edit completion callback id */
   void       *keytree;          /* user input keyword binary tree root */
   int         keep;             /* flag indicating to keep previous keywords */
   char       *rejected;         /* points to rejected parameter text */
   char       *ucamessage;       /* last UCA error message */
   char        stamessage[SW];   /* status message */
   char        lstmessage[SW];   /* process' last status message */
   state       state;            /* task state     */
   state       prevstat;         /* previous state */
   enum { EXPERIENCED, NORMAL, TEST } output_mode;
   int         status_mask;      /* status message mask */
   int         reqsize;          /* size of current request message */
   int         reqlen;           /* remainder of current request message */
   int         reqpid;           /* pid of current request message */
   char        workdir[DIRLEN+1];/* working directory */
   UnitedRequest request;        /* current request */
   char       *reply;            /* reply buffer */
   int         reply_in;         /* buffer pointer */
   int         reply_size;       /* current buffer size */
   char        stdobuf[LINLEN+1];/* buffer for stdout&stderr output */
   int         stdo_in;          /* next element to be filled */
   void       *keyfile;          /* file pointer for allpar */
   int         waitkeyread;      /* number of keywords to wait for */
   struct _TaskStatus *keydest;  /* task expected to read the keywords */
   char        xkey[KEYLEN];     /* status keyword for XEQCONT caller */
} *TaskStatus;

/* ========================================================================= */
/*                         function declarations                             */
/* ------------------------------------------------------------------------- */

extern void       put_coa    (TaskStatus,char*);           /* see note below */
extern void       put_psa    (TaskStatus,char*,int);
extern void       put_uca    (TaskStatus,char*);
extern void       err_uca    (TaskStatus,char*);
/* Note: put_coa is declared in this file and not in e.g. coamgr.h because
 *       it must be available for every version of Hermes and its
 *       implementation may be different for different versions.
 */

#include "alloc.h"
#include "altkeysock.h"
#include "changecase.h"
#include "dcdrange.h"
#include "execpath.h"
#include "do_request.h"
#include "hermesipc.h"
#include "parmgr.h"
#include "parse.h"
#include "put_log.h"
#include "rdefaults.h"
#include "substi.h"
#include "taskmgr.h"
#include "filecontents.h"
#include "readcontext.h"
#include "editfile.h"
#include "taskoutput.h"
#include "dcdparse.h"
#include "dcderrstr.h"
#include "notifier.h"

#if defined(TESTLOG)
#define PRINTF printf
#define PERROR perror
#else
void PRINTF(char*,...);
void PERROR(char*);
#endif

#endif  /* DO NOT ADD ANYTHING AFTER THIS LINE */
#<

#>            hermesipc.h
#if !defined(_hermesipc_h_)
#define _hermesipc_h_
extern int        get_request(TaskStatus);
extern void       local_msg  (TaskStatus,RequestMessage*);
extern int        reply_alloc(TaskStatus,int);
extern void       reply_free (TaskStatus);
extern void       reply_send (TaskStatus,int);
extern void       reply_put  (TaskStatus,char*,int);
#endif
#<

#>            keyboard.h
/* keyboard.h
*/
#if !defined(_keyboard_h_)
#define _keyboard_h_
extern void InitKey(int);
extern long InsertKeyboardProc(int(*)(),void*);
extern void RemoveKeyboardProc(long);
extern void SuspendKeyboard(void);
extern void ResumeKeyboard(void);
extern void Type(int);
#define QRESCHED 15   /* asks keyboard handler if it supports rescheduling(^O)*/
#define YRESCHED -1   /* affirmative reply from keyboard handler */
#define XRESCHED -2   /* actual request to handler to take action */
#endif
#<

#>            keysym.h
/* keysym.h
*/
#if !defined(_keysym_h_)
#define _keysym_h_

/*
 * special character definitions:
 */

#define CTRL_A  1   /* control keys */
#define CTRL_B  2
#define CTRL_C  3
#define CTRL_D  4
#define CTRL_E  5
#define CTRL_F  6
#define CTRL_G  7
#define CTRL_H  8
#define CTRL_I  9
#define CTRL_J 10
#define CTRL_K 11
#define CTRL_L 12
#define CTRL_M 13
#define CTRL_N 14
#define CTRL_O 15
#define CTRL_P 16
#define CTRL_Q 17
#define CTRL_R 18
#define CTRL_S 19
#define CTRL_T 20
#define CTRL_U 21
#define CTRL_V 22
#define CTRL_W 23
#define CTRL_X 24
#define CTRL_Y 25
#define CTRL_Z 26
#define TAB CTRL_I
#define LF  CTRL_J
#define CR  CTRL_M
#define ESC 27

#define DEL     0177   /* rubout character */

#define ESCBIT  0200   /* codes generated by ESC handler */
#define CXBIT   0400   /* codes generated bu CTRL-X handler */

/* Some ANSI keys: */

#define UP        01004
#define DOWN      01001
#define LEFT      CTRL_B
#define RIGHT     CTRL_F
#define PAGEUP    CTRL_Z
#define PAGEDOWN  CTRL_V
#define HOME      01002
#define END       01003

#define WEBHELP   TAB

#endif  /* DO NOT ADD ANYTHING AFTER THIS LINE */
#<

#>            keytext.h
#if !defined(_keytext_h_)
#define _keytext_h_
extern char *KeyText(char *key, char wild);
#endif
#<

#>            khnodemgr.h
#if !defined(_khnodemgr_h_)
#define _khnodemgr_h_

#define KH_MAXREFS 9
typedef struct _khnode {
   struct _khnode  *next;   /* next entry in linked list */
   char   *ref[KH_MAXREFS]; /* reference names           */
   int    nrefs;            /* number of references      */
   int    ncols;            /* width of help text        */
   int    nlines;           /* number of text lines -1   */
   int    linesall;         /* number of lines allocated */
   int    col;              /* current column            */
   int    mapline;          /* first line in window      */
   char   *text;
} _khnode, *khnode;         /* key help node */

extern khnode CreateKhnode(char *text, int width);
extern void   AppendKhnode(khnode node, char *text);
extern void   DeleteKhnode(khnode node);
#endif
#<

#>            mainloop.h
#if !defined(_mainloop_h_)
#define _mainloop_h_
extern void InitMain(int);
extern void MainLoop(void);
extern long InsertMessageProc(void(*)(),int,void*);
extern long InsertTimerProc(void(*)(),int,void*);
extern void RemoveMessageProc(long);
#endif
#<

#>            minimatch.h
#if !defined(_minimatch_h_)
#define _minimatch_h_
extern int MatchKey(char *taskname, char *keyin, char *keyout);
extern int MatchTask(char *namein, char *nameout);
#endif
#<

#>            monopoly.h
#if !defined(_monopoly_h_)
#define _monopoly_h_
typedef void(* MonExitProc)(int);
extern void Monopoly(char *path, char *arg, MonExitProc exproc);
#endif
#<

#>            nhermescom.h
/* nhermescom.h
*/
#if !defined(_nhermescom_h_)
#define _nhermescom_h_
#include "hermescom.h"
#include "mainloop.h"

typedef struct _Environment {
   TaskStatus task;                /* task status block */
} *Environment;

typedef struct {
   char *logfile;              /* name of logfile                             */
   char *coafile;              /* name of COA (pseudo screen-) file           */
   int  ptime;                 /* max. processing time of NHermes             */
   char **usercommands;        /* user commands to be started by NHermes      */
} Arguments;

extern void MainLoop(void);
extern long InsertMessageProc(void(*)(),int,void*);
extern void RemoveMessageProc(long);
extern void InitMain(int);
extern Arguments *GetArguments(char**);
#endif  /* DO NOT ADD ANYTHING AFTER THIS LINE */
#<

#>            notifier.h
#if !defined(_notifier_h_)
#define _notifier_h_
extern char *NotifyName(void);
extern int StartNotify(TaskStatus curstat);
extern void StopNotify(TaskStatus curstat);
extern void NotifyTask(TaskStatus curstat, char *event, int nbytes);
extern void FinisNotify(void);
#endif
#<

#>            oshell.h
#if !defined(_oshell_h_)
#define _oshell_h_
extern void oshell(void);
#endif
#<

#>            parmgr.h
#if !defined(_parmgr_h_)
#define _parmgr_h_
typedef void(*ParProc)(long,char*,void*);
extern void       init_par   (TaskStatus);
extern void       insert_par (TaskStatus,char*,char*,int);
extern char       *get_par   (TaskStatus, char*);
extern void       cancel_par (TaskStatus,char*);
extern void       reject_par (TaskStatus,char*);
extern void       delete_par (TaskStatus,char*);            /* obsolescent */
extern int        find_par   (TaskStatus,char*,char*,int);  /* obsolescent */
extern void       update_par (void);
extern void       close_par  (void);
extern int        find_all   (char*,char*,int);
extern int        write_par  (char *taskname, char *filename);
extern int        write_rcl(char *taskname, char *key, char *filename);
extern char       *display_par (char*,int);
extern long       InsertParProc (ParProc,void*);
extern void       RemoveParProc (long);
#endif
#<

#>            parse.h
#if !defined(_parse_h_)
#define _parse_h_
extern int        ParseCmd   (char *cmd);
extern long       CmdGetInfo (void);
extern void       CmdParam   (long,char **key, char **value);
extern int        CmdMacro   (long);
extern int        CmdNkeys   (long);
extern char*      CmdTaskName(long);
extern char*      CmdExecName(long);
extern void       CmdDelete  (long);
extern char*      CmdErrString(int);
/*
 *   error codes
 */
#define BADSYNTAX -1
#define PTOOLONG  -2
#define TTOOLONG  -3
#define PCONFLICT -4
#define NOTSKNAME -5
#define CTOOLONG  -6
#define KTOOLONG  -7
#endif
#<

#>            parsekeyhelp.h
#if !defined(_parsekeyhelp_h_)
#define _parsekeyhelp_h_
typedef void(*KhPutWord)(char*,int);
typedef void(*KhPutRefWord)(char*,int);
typedef void(*KhNewLine)(void);
typedef void(*KhPutReference)(char*);
extern void ParseKeyHelp(char*,KhPutWord,KhNewLine,KhPutRefWord,KhPutReference);
#endif
#<

#>            printmgr.h
#if !defined(_printmgr_h_)
#define _printmgr_h_
extern void SelectPrinter(void);
extern int SetPrinter(int);
extern int PrintFile(char*);
#endif
#<

#>            prompter.h
#if !defined(_prompter_h_)
#define _prompter_h_
#define PROMPT_ENTER  1
#define PROMPT_CANCEL 2
typedef int (*PrompterProc)(long, int, char*, void*);
extern long CreatePrompter(char *promptstring, PrompterProc callback, void *arg);
extern long SetPrompterPos(long id, int y, int x);
extern long SetPrompterDefault(long id, char *defaultstring);
extern long SetPrompterError(long id, char *errorstring);
extern long SetPrompterHelp(long id, char *document);
extern long SetPrompterActive(long id, char *activestring);
extern long ActivatePrompter(long id);
extern void PrompterType(long id, char c);
extern char *PromptValue(long id);
extern void DeletePrompter(long id);
extern long Prompter (char *prompt, char *dflt, PrompterProc callback, void *arg,
                      int row, int col);
#endif
#<

#>            put_log.h
#if !defined(_put_log_h_)
#define _put_log_h_
extern void       put_log    (TaskStatus,char*);
extern void       chlogname  (char*);
#endif
#<

#>            rdefaults.h
#if !defined (_rdefaults_h_)
#define _rdefaults_h_
extern int rdefaults(TaskStatus);
#endif
#<

#>            readcontext.h
#if !defined(_readcontext_h_)
#define _readcontext_h_
extern void       ReadContext(TaskStatus);
extern char*      GetTaskDir(void);
extern int        SetTaskDir(char*);
extern int        MatchOn(void);
extern int        BeepOn(void);
extern char*      WebBrowser(void);
extern int        StartBrowser(void);
#endif
#<

#>            search.h
#if !defined(_search_h_)
#define _search_h_
extern void Search(int direction);
#endif
#<

#>            showexpr.h
#if !defined(_showexpr_h_)
#define _showexpr_h_
extern void ShowExpr(void);
#endif
#<

#>            showkeys.h
#if !defined(_showkeys_h_)
#define _showkeys_h_
extern void ShowKeys(char *);
#endif
#<

#>            substi.h
#if !defined(_substi_h_)
#define _substi_h_
extern void       sbinit     (int);
extern int        sbspec     (TaskStatus,char*,char*);
extern void       sbstte     (TaskStatus,char*);
extern void       rev_sbstte (TaskStatus,char*);
extern void       sbback     (TaskStatus);
#endif
#<

#>            taskmgr.h
#if !defined(_taskmgr_h_)
#define _taskmgr_h_
typedef void (*TaskProc)(long,TaskStatus,void*);
extern TaskStatus inittask  (int,int,int);
extern TaskStatus Exec      (TaskStatus,char*);
extern TaskStatus pidtask(int pid);
extern void       rundown   (TaskStatus);
extern int        starttask (TaskStatus,char*);
extern void       aborttask     (TaskStatus);
extern void       suspend   (TaskStatus);
extern void       resume    (TaskStatus);
extern long       InsertTaskProc (TaskProc,void*);
extern void       RemoveTaskProc (long);
extern void       TaskChanged (TaskStatus);
extern void       ExitChan(int);
extern void       NotifyCaller(TaskStatus task, int mode);
#endif
#<

#>            taskoutput.h
#if !defined(_taskoutput_h_)
#define _taskoutput_h_
extern void EnableTskOut(TaskStatus task);
extern void DisableTskOut(TaskStatus task);
#endif

#<

#>            tekwindow.h
#if !defined(_tekwindow_h_)
#define _tekwin_h_
void TekWindow(void);
int  SelectTek(char*);
#endif
#<

#>            terminal.h
#if !defined(_terminal_h_)
#define _terminal_h_
extern void Terminal();
#define HERTERM 0
#define PRVTERM 1
#endif
#<

#>            thermescom.h
/* thermescom.h
*/
#if !defined(_thermescom_h_)
#define _thermescom_h_

#define DNTASK    4                /* default maximum number of tasks */
#define IPROC    16                /* initial number of process status blocks */
#define NSUB     40                /* initial number of keyword substitutions */
#define NUCA      2                /* number of command area lines */
#define ETIMERS  10                /* estimated number of timer procedures */
#define NMESH    (2*IPROC+ETIMERS+5) /* initial number of message handlers */
#define NKEYH    15                /* initial number of keyboard handlers */
#define NHINTS    2                /* max. number of problem report hints */
#define HINTTIME  0                /* report hint display time in seconds */

#include "hermescom.h"
#include "mainloop.h"
#include "windows.h"
#include "keyboard.h"
#include "prompter.h"
#include "coamgr.h"
#include "ushell.h"
#include "altkeyboard.h"
#include "contextmenu.h"
#include "showkeys.h"
#include "printmgr.h"
#include "ucamgr.h"
#include "help.h"
#include "helpkey.h"
#include "oshell.h"
#include "tekwindow.h"
#include "terminal.h"
#include "aiduca.h"
#include "monopoly.h"
#include "editkeys.h"
#include "cmdstack.h"
#include "keysym.h"
#include "search.h"
#include "showexpr.h"
#include "calculator.h"
#include "webhelp.h"

typedef struct _Environment {
   Window     tsa;                 /* task status area */
   TaskStatus task;                /* task status block */
} *Environment;

extern char *NameFromUca(void);
extern char *KeyFromCmd(char *buffer);
extern TaskStatus NameToTask(char *name);
extern char *NameFromUca(void);
extern char *NameFromCmd(char *buffer);
extern char *KeyFromUca(void);
extern void TimeDelWin(long id, Window win);
extern Window FlashText(int, int, char*, int);
extern int KeyDelWin(long id, int c, Window win);
#endif  /* DO NOT ADD ANYTHING AFTER THIS LINE */
#<

#>            ucamgr.h
#if !defined(_ucamgr_h_)
#define _ucamgr_h_

#define UCA_PROMPT 1
#define UCA_RETURN 2
#define UCA_ABORT  3
#define UCA_WAITGO 4
#define UCA_CYCLE  5
#define UCA_SPACE  6
#define UCA_FBLANK 7
#define UCA_EQUAL  8
#define UCA_CLEAR  9

typedef void (*UcaProc)(long,int,void*);

extern void  InitUca(void);
extern void  UcaType(char c);
extern void  ClearUca(void);
extern bool  UcaFree(void);
extern void  FreeUca(void);
extern char* GetUca(void);
extern int   GetUcaPtr();
extern void  PutUca(char*);
extern long  InsertUcaProc(UcaProc proc, void *arg);
extern void  RemoveUcaProc(long id);
#endif
#<

#>            ushell.h
#if !defined(_ushell_h_)
#define _ushell_h_
extern int ushell(void);
#endif
#<

#>            varmgr.h
#if !defined(_varmgr_h_)
#define _varmgr_h_
#include "dcdcom.h"

extern int   IsVar(char *name);
extern Value GetVar(char *name);
extern int   PutVar(char *name, Value val);
extern Value StrVal(char *name);
extern Value NewList(int size);
extern void  FreeVar(Value *val);
extern void  CleanVar(void);
extern void  MaxList(int size);
#endif /* _varmgr_h_ */
#<

#>            webhelp.h
#if !defined(_webhelp_h_)
#define _webhelp_h_
extern void InitWebHelp(void);
extern int  WebHelp(char *name);
#endif
#<

#>            windows.h
/* windows.h
*/
#if !defined(_windows_h_)
#define _windows_h_
typedef struct _Window {
   int     mapped;          /* flag indicating whether window is mapped */
   int     framed;          /* indicates whether a border has been drawn */
   int     private;         /* indicates whether backup is private */
   char    *backup;         /* in-memory backup window */
   int     x;               /* origin of window */
   int     y;
   int     nx;              /* dimension of window */
   int     ny;
   struct _Window *next;    /* pointer to next (=deeper) window */
} *Window,_Window;

extern void InitWin(void);
extern void FinisWin(void);
extern Window CreateWindow(int,int);
extern void DeleteWindow(Window);
extern void UnmapWindow(Window);
extern void MapWindow(Window,int,int);
extern void PutChar(Window,int,int,char);
extern void RefreshWindow(Window);
extern void ClearWindow(Window);
extern void PutString(Window,int,int,char*);
extern void FrameWindow(Window);
extern void PutLine(Window,int,char*);
extern void UnmapWindow(Window);
extern void PutCursor(Window,int,int);
extern void RedrawWindow(Window);
extern void RedrawScreen(void);
extern void RepairScreen(void);
extern void ScrollWindow(Window,int);
extern void SuspendScreen(void);
extern void ResumeScreen(void);
extern void Beep(void);
extern void SwitchBeep(void);
#endif /* DO NOT ADD ANYTHING AFTER THIS LINE */
#<

#>            y.tab.h

typedef union  {
       Value  valval;
       Arglst argval;
       } YYSTYPE;
extern YYSTYPE yylval;
# define NUMBER 257
# define IDENTIFIER 258
# define STRING 259
# define POWER 260
# define WHITE 261
# define LEXERR 262
# define TEXTFILE 263
# define DESCR 264
# define IMAGE 265
# define TABLE 266
# define UMINUS 267
# define REPEAT 268
# define BADNUM 269
#<

#>            absname.c
/* absname.c
                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

Translate relative file name into absolute.
Author: J.P. Terlouw
*/

#include "string.h"
#include "hermescom.h"
#include "alloc.h"
#include "absname.h"

extern TaskStatus Template;

static char *buffer=NULL;
static int  buflen=0;

/* ========================================================================== */
/*                                 absname                                    */
/* -------------------------------------------------------------------------- */
/*  absname() translates a file name into an absolute filename by prefixing
 *  the specified directory string. If the directory string pointer is NULL,
 *  the template task status block working directory is used instead.
 */
char *absname(char *dir, char *name)
{
   int newlen;
   int dirlen=0;
    
   newlen = strlen(name)+1;
   if (*name != '/') {
      if (dir) dirlen = strlen(dir);
      else dirlen = strlen(Template->workdir);
      newlen += dirlen+1;
   } 
   if (newlen>buflen) {
      if (!buffer) buffer = Malloc(newlen);
      else         buffer = Realloc(buffer,newlen);
      buflen = newlen;
   }
   if (*name != '/') {
      if (dir) strcpy(buffer,dir);
      else     strcpy(buffer,Template->workdir);
      strcat(buffer,"/");
      strcat(buffer,name);
   } else strcpy(buffer,name);

   return buffer;
}
#<

#>            aiduca.c
/* aiduca.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It is responsible for aiding ucamgr
with respect to taskname and keyword completion in tHermes.

Author: J.P. Terlouw.
*/

#include "stddef.h"
#include "string.h"
#include "thermescom.h"
#include "ucamgr.h"
#include "minimatch.h"
#include "aiduca.h"

static void deactivate(void);
static void ucahandler(long,int,void*);

static long uid=0;

/* ========================================================================== */
/*                               AidUca                                       */
/* -------------------------------------------------------------------------- */
/*  AidUca switches the taskname- and keyword completion module on or off.
*/
extern void AidUca(void)
{
   if (uid) {
      deactivate();
   } else {
      uid = InsertUcaProc(ucahandler,NULL);
   }
}

/* -------------------------------------------------------------------------- */
/*                               ucahandler                                   */
/* -------------------------------------------------------------------------- */
static void ucahandler(long id, int reason, void *arg)
{
   char taskname[NAMLEN+1];
   TaskStatus task=NULL;
   char keyout[KEYLEN+1];
   char keyin[KEYLEN+1];
   char *bufpt;
   int  i, n, nmatch, index;
   long cmdid;

   switch (reason) {
                                       /* === First blank on command line === */
      case UCA_FBLANK:
      case UCA_RETURN:
           if (reason==UCA_RETURN) UcaType(CTRL_E);
           bufpt = GetUca();
           index = GetUcaPtr();
           if (reason==UCA_RETURN && bufpt[index-1]==' ') return;
           for (i=0; i<index-1; i++) {
              if (bufpt[i]==' ') return;        /* not really first blank ... */
           }
           if (ParseCmd(bufpt)) break;               /* error in command line */
           cmdid = CmdGetInfo();
           strcpy(taskname,CmdExecName(cmdid));
           CmdDelete(cmdid);
           n = strlen(taskname);
           nmatch = MatchTask(taskname,taskname);
           if (nmatch==1) {
              if (reason==UCA_FBLANK) UcaType(CTRL_B); else index++;
              if (bufpt[index-2]==')') UcaType(CTRL_B);
              for (i=0; i<n; i++) UcaType(DEL);
              n = strlen(taskname);
              for (i=0; i<n; i++) UcaType(taskname[i]);
              UcaType(CTRL_E);
           }
           break;

                                      /* === Equals sign typed === */
      case UCA_EQUAL:
           bufpt = GetUca();
           index = GetUcaPtr();
           if (ParseCmd(bufpt)) break;             /* error in command line */
           cmdid = CmdGetInfo();
           task = NameToTask(CmdTaskName(cmdid));
           if (task) {
              ProcStatus proc;
              for (proc=task->proc; proc->next; proc=proc->next) {;}
              strcpy(taskname,proc->name);
              StringLow(taskname);
           } else strcpy(taskname,CmdExecName(cmdid));
           while (index && bufpt[index-1]!=' ' && bufpt[index-1]!=',') {
             /* move to start of keyword */
              index--;
           }
           n = GetUcaPtr()-index;
           strncpy(keyin,bufpt+index,n);
           keyin[n] = '\0';
           StringUp(keyin);
           StringLow(taskname);
           nmatch = MatchKey(taskname,keyin,keyout);
           if (nmatch==1) {
              /* unique match found */
              UcaType(CTRL_B);            /* Beware! Never Ever Type '=' !!! */
              n = strlen(keyin)-1;
              for (i=0; i<n; i++) UcaType(DEL);     /* erase existing keyword */
              n = strlen(keyout)-1;
              for (i=0; i<n; i++) UcaType(keyout[i]);  /* write matched keyword */
              UcaType(CTRL_F);
           } else if (nmatch>1) {
              UcaType(DEL);
              Beep();
           } else Beep();
           CmdDelete(cmdid);
           break;

                           /* === Other codes: irrelevant to this module === */
      default: break;
   }
}

/* -------------------------------------------------------------------------- */
/*                               deactivate                                   */
/* -------------------------------------------------------------------------- */
static void deactivate(void)
{
   RemoveUcaProc(uid);
   uid = 0;
}
#<

#>            alloc.c
/*
 *  alloc.c
 *
 *  Checking substitute for malloc, realloc and calloc.
 *  Its sole purpose is to eliminate numerous checks in different places
 *  where graceful correction of the problem of allocation failure is
 *  not possible.
 *
 *  The function StrDup returns a pointer to a duplicate of the argument.
 *
 *  It just stops the program in case of allocation failure.
 *
 */
#include "alloc.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

static void Fail(void);

#define RCHECK if (ptr) return ptr; else Fail();

static void *ptr;

extern void *Malloc(int size)
{
   ptr = malloc(size);
   RCHECK
}

extern void *Realloc(void *orig, int newsize)
{
   ptr = realloc((char*)orig, newsize);
   RCHECK
}

extern void *Calloc(int nelem, int elsize)
{
   ptr = calloc(nelem, elsize);
   RCHECK
}

extern void Free(void *ptr)
{
   if (ptr)
      free(ptr);
   else {
       printf("\nDynamic memory de-allocation failure\n");
       exit(1); /* When this happens, it is due to a programming error */
   }
}

extern char *StrDup (char *orig)
{
   char *result=NNew(strlen(orig)+1,char);
   strcpy(result,orig);
   return result;
}


static void Fail(void)
{
   printf("\nDynamic memory allocation failure\n");
   exit(1); /* Hope this does not happen. Don't know what to do otherwise... */
}
#<

#>            altkeyboard.c
/* altkeyboard.c
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes.
Author: J.P. Terlouw.
*/
#include "stddef.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#include "mainloop.h"
#include "keyboard.h"
#include "altkeysock.h"

static void AcceptAltKeyboard(long id, int fd, void *arg);
static void ReadAltKeyboard(long id, int fd, void *arg);

/* ========================================================================== */
/*                             InitAltKey                                     */
/* -------------------------------------------------------------------------- */
/*  InitAltKey initializes the 'alternative keyboard' module.
 *  The alternative keyboard is a mechanism that allows other processes to
 *  'type' into the user command area. The communication is realized using
 *  a socket. This socket is obtained from AltKeySock().
 *  Processes can connect to this socket and write any characters to be
 *  'typed' in the user command area. If a zero byte is received, or if the
 *  client socket is closed, then Hermes also closes its communication socket.
 *  InitAltKey fails quietly, i.e. if it cannot perform its function, then
 *  the alternative keyboard functionality is simply not present.
 */

void InitAltKey(void)
{
   int s;

   s = AltKeySock();
   if (s>0) {
      (void)InsertMessageProc(AcceptAltKeyboard, s, NULL);
      listen(s,5);
   }
}

/*
 *  AcceptAltKeyboard accepts connections on the socket created by InitAltKey.
 *  and schedules reading of the characters by ReadAltKeyboard.
 */
static void AcceptAltKeyboard(long id, int s, void *arg)
{
   (void)InsertMessageProc(ReadAltKeyboard, AcceptAltKey(s), NULL);
}

/*
 *  ReadAltKeyboard reads a character from its socket and passes it to
 *  Type which will 'type' it in the user command area.
 *  If an error is encountered or a zero byte has been read, ReadAltKeyboard
 *  assumes that the client process wants to close the connection or has
 *  closed it already or has exited. In this case the socket is closed and
 *  ReadAltKeyboard de-schedules itself.
 */
static void ReadAltKeyboard(long id, int fd, void *arg)
{
   char c;

   if (read(fd,&c,1)==1 && c!=0) Type((int)c); else {
      close(fd);   /* shutdown(fd,2); */
      RemoveMessageProc(id);
   }
}

#<

#>            altkeysock.c
/* altkeysock.c
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes.
Author: J.P. Terlouw.
*/

#include "stdio.h"
#include "stdlib.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#include "altkeysock.h"


/* ========================================================================== */
/*                             AltKeySock                                     */
/* -------------------------------------------------------------------------- */
/*  AltKeySock creates the socket for the 'alternative keyboard' module to
 *  listen to. (The alternative keyboard is a mechanism that allows other
 *  processes to 'type' into the user command area.)
 *  The communication is realized using Internet sockets.
 *  The node name and port number of the socket are written in the file
 *  designated by the environment variable ALT_KEYBOARD (separated by two
 *  colons, eg. rugfx4::7032).
 */

extern int AltKeySock(void)
{
   int s;
   int length;
   struct sockaddr_in  altkb;
   char *filename;
   char host[65];
   FILE *f;

   filename = getenv("ALT_KEYBOARD");
   if (!filename) return -1;              /* environment variable not defined */
   f = fopen(filename, "w");
   if (!f) return -1;                     /* file could not be opened         */
   s = socket(AF_INET,SOCK_STREAM, 0);
   if (s<0) return -1;                    /* socket could not be created      */
   altkb.sin_family = AF_INET;
   altkb.sin_addr.s_addr = INADDR_ANY;
   altkb.sin_port = 0;
   length = sizeof(altkb);
   if (bind(s, (struct sockaddr*)&altkb, length) <0 ) return -1;
   if (getsockname(s, (struct sockaddr*)&altkb, &length) <0 ) return -1;
   gethostname(host,65);
   fprintf(f,"%s::%d\n",host,(int)ntohs(altkb.sin_port));
   fclose(f);
   return s;
}

/* ========================================================================== */
/*                           AcceptAltKey                                     */
/* -------------------------------------------------------------------------- */
/*  AcceptAltKey accepts a connection on an alternative keyboard socket.
 */
extern int AcceptAltKey(int s)
{
   struct sockaddr_in altkb;
   int                aklen;

   aklen = sizeof(altkb);
   return accept(s, (struct sockaddr*)&altkb, &aklen);
}

/* ========================================================================== */
/*                           RmAltKeySock                                     */
/* -------------------------------------------------------------------------- */
/*  RmAltKeySock removes the associated file of an alternative keyboard socket.
 */
extern void RmAltKeySock(void)
{
   char *filename;

   filename = getenv("ALT_KEYBOARD");
   if (filename) remove(filename);
}
#<

#>            bintree.c
/* bintree.c
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

Binary tree package for user input parameter administration.

Reference: Knuth, D.E., The Art Of Computer Programming,
           Volume 3 / Sorting and Searching (1973) 422-447

This module is private to Hermes.
Author: J.P. Terlouw.
*/
#include "stddef.h"
#include "string.h"
#include "bintree.h"

static Node *FindNode(Tree tree, char *name);

/* ========================================================================== */
/*                              InsertNode                                    */
/* -------------------------------------------------------------------------- */
/*
 *  InsertNode() inserts a node in a tree structure.
 *
 *  Arguments:  tree  --  pointer to the root of the tree;
 *              node  --  node to be inserted. The name of the node must have
 *                        been set before and must not be changed afterwards.
 *
 *  Returns:              a NULL pointer if the node was inserted, otherwise a
 *                        pointer to the node with the same name.
 */
extern Node InsertNode(Tree tree, Node node)
{
   Node *target;

   target = FindNode(tree, node->name);
   if (*target)
      return *target;                                 /* name already present */

   *target = node;
   node->left  = 0;
   node->right = 0;
   return 0;
}

/* ========================================================================== */
/*                             RetrieveNode                                   */
/* -------------------------------------------------------------------------- */
/*
 *  RetrieveNode() retrieves a node from a tree structure.
 *
 *  Arguments:  tree  --  pointer to the root of the tree;
 *              name  --  name of the node to be retrieved.
 *
 *  Returns:              a pointer to the node, or NULL if not found.
 */
extern Node RetrieveNode(Tree tree, char *name)
{
   return *FindNode(tree,name);
}

/* ========================================================================== */
/*                            TraverseTree                                    */
/* -------------------------------------------------------------------------- */
/*
 *  TraverseTree() visits every node of the specified tree in alfabetical order
 *  and calls the specified action function.
 *
 *  Arguments:  tree   --  pointer to the root of the tree;
 *              action --  pointer to the function which will be called
 *                         for every node in the tree.
 *              tparg  --  second argument to 'action'.
 *
 *  The prototype of 'action' is: void(*)(Node,void*)
 *
 */
extern void TraverseTree(Tree tree, TraverseProc action, void *tparg)
{
   Node right;            /* own right pointer because action may delete node */

   if (*tree) {
      right = (*tree)->right;
      TraverseTree(&(*tree)->left,action,tparg);
      action(*tree,tparg);
      TraverseTree(&right,action,tparg);
   }
}

/* ========================================================================== */
/*                             UnlinkNode                                     */
/* -------------------------------------------------------------------------- */
/*
 *  UnlinkNode() removes a node from a tree structure.
 *
 *  Arguments:  tree   --  pointer to the root of the tree;
 *              name   --  name of the node to be removed.
 *
 *  Returns:    a pointer to the removed node, or NULL if not found.
 */
extern Node UnlinkNode(Tree tree, char *name)
{
   Node *target, current, previous, next;

   target = FindNode(tree, name);
   current = *target;
   if (current) {
      if (!current->right) {
         *target = current->left;
      } else if (!current->left) {
         *target = current->right;
      } else {
         next = current->right;
         if (next->left) {
            do {
               previous = next;
               next = next->left;
            } while(next->left);
            previous->left = next->right;
            next->right = current->right;
         }
         next->left = current->left;
         *target = next;
      }
   }
   return current;
}

/* -------------------------------------------------------------------------- */
/*                              FindNode                                      */
/* -------------------------------------------------------------------------- */
/*  FindNode() searches the specified tree and returns a pointer to the pointer
 *  in the tree structure pointing to the node with the specified name or
 *  to the zero pointer to which a node with the specified name should be
 *  attached.
 *
 *  Arguments:  tree  --  pointer to the root of the tree;
 *              name  --  the name to be found.
 *
 *  Returns:              a pointer to the pointer found in the tree.
 */
static Node *FindNode(Tree tree, char *name)
{
   Node *current=tree;
   int cmp;

   while (*current && (cmp=strcmp(name,(*current)->name))) {
      if (cmp<0) {
         current = &(*current)->left;
      } else {
         current = &(*current)->right;
      }
   }
   return current;
}
#<

#>            calculator.c
/* calculator.c
                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It implements a simple calculator.
Author: J.P. Terlouw.
*/

#define XPOS COLS-60
#define YPOS LINES-NTASK-NUCA-3

#include "stddef.h"
#include "string.h"
#include "thermescom.h"
#include "prompter.h"
#include "dcdparse.h"
#include "calculator.h"

extern int LINES, COLS, NTASK;

static int  evaluate(long id, int status, char *expr, void *arg);
static void getvals(long id, ExprVal *result, void* calldata);

static long prid=0;
static long exprid=0;
static char valstr[80];
static char defstr[80];
static char *dsptr=NULL;

static char help[]="*hermes/calculator.html";

/* ========================================================================== */
/*                                 calculator                                 */
/* -------------------------------------------------------------------------- */
/*  Activate or deactivate the calculator
 */
extern void calculator(void)
{
   if (!prid) {
      prid = Prompter("Enter expression:                                   ", 
                      dsptr, evaluate, NULL, YPOS, XPOS);
      (void)SetPrompterHelp(prid,help);
   } else {
      DeletePrompter(prid);
      prid = 0;
   }
}

static int evaluate(long id, int status, char *expr, void *arg)
{
   if (status==PROMPT_CANCEL) {
      prid = 0;
      return 0;
   } else {
      exprid = InsertExprProc(getvals,NULL);
      (void)EvalExpr(expr);
      strncpy(defstr,expr,80);
      dsptr=defstr;
      (void)SetPrompterError(prid, valstr);
      RemoveExprProc(exprid);
      return -1;
   }
}

static void getvals(long id, ExprVal *result, void* calldata)
{
   strncpy(valstr, result->value, 80);
}
      
#<

#>            callback.c
/* callback.c
                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It implements a general purpose callback
facility.

Author: J.P. Terlouw
*/

#include "stdlib.h"
#include "stddef.h"
#include "callback.h"

typedef struct _Callback {
   CallbackProc     proc;                 /* callback function pointer */
   void             *cldata;              /* client data               */
   struct _Callback *next;                /* link to next block        */
} _Callback, *Callback;

typedef Callback *ChainHead;

/* ========================================================================== */
/*                                 RegisterCb                                 */
/* -------------------------------------------------------------------------- */
/*  RegisterCb() registers a callback function with the callback chain
 *  specified in the first argument. This chain must have been created
 *  by a call to NewCbChain(). It returns a callback identification. 
 *
 *  The prototype of the callback function is:
 *
 *     void proc(CallbackId id, void *reason, void *cldata);
 *
 *  id      is the callback identification;
 *  reason  information supplied by the caller of InvokeCb();
 *  cldata  information supplied by the caller of RegisterCb().
 */
CallbackId RegisterCb(CallbackChain chain, CallbackProc proc, void *cldata)
{
   ChainHead head=(ChainHead)chain;
   Callback cb;
   
   cb = calloc(1,sizeof(_Callback));
   if (!cb) return NULL;
   cb->proc   = proc;
   cb->cldata = cldata;
   cb->next   = *head;
   *head      = cb;
   return (CallbackId)cb;
}

/* ========================================================================== */
/*                                 DeregisterCb                               */
/* -------------------------------------------------------------------------- */
/*  DeregisterCb() deregisters a callback from the specified chain.
 */
void DeregisterCb(CallbackChain chain, CallbackId id)
{
   ChainHead head=(ChainHead)chain;
   Callback current=*head;
    
   while (current) {
      if ((CallbackId)current==id) {
         current->proc = NULL;
         break;
      }
      current = current->next;
   }
}

/* ========================================================================== */
/*                                 NewCbChain                                 */
/* -------------------------------------------------------------------------- */
/*  NewCbChain() creates a new callback chain.
 */
CallbackChain NewCbChain(void)
{
   ChainHead head = calloc(1,sizeof(ChainHead));
   return (CallbackChain)head;
}

/* ========================================================================== */
/*                                 DeleteCbChain                              */
/* -------------------------------------------------------------------------- */
/*  DeleteCbChain() deletes an existing callback chain. If there are still
 *  registered callbacks, the are first removed.
 */
void DeleteCbChain(CallbackChain chain)
{
   ChainHead head=(ChainHead)chain;
   Callback current=*head;
    
   while (current) {
      current = current->next;
      free(current);
   }
   free(head);
}

/* ========================================================================== */
/*                                 InvokeCb                                   */
/* -------------------------------------------------------------------------- */
/*  InvokeCb() invokes all registered callbacks in the specified chain.
 *  The reason is passed to every callback function.
 */
void InvokeCb(CallbackChain chain, void *reason)
{
   ChainHead head=(ChainHead)chain;
   Callback cb, *i, *next;
    
   if (!chain) return;
   for ( i=(Callback*)head; *i; i=next) {
      cb = *i;
      next = &cb->next;
      if (cb->proc) {
         (*cb->proc)((CallbackId)cb, reason, cb->cldata);
      } else {
         /* invalidated block: release */
         next = i;  *i = cb->next;  free(cb);
      }
   }
}
#<

#>            changecase.c
/*
 *  changecase.c
 *
 *  Utility to change character strings to upper or lower case.
 */
#include "ctype.h"
#include "changecase.h"
extern void StringUp(char *string)
{
   char c;

   while (*string) {
      c = *string;
      *(string++) = islower(c)?toupper(c):c;
   }
}
extern void StringLow(char *string)
{
   char c;

   while (*string) {
      c = *string;
      *(string++) = isupper(c)?tolower(c):c;
   }
}

#<

#>            cmdstack.c
/* cmdstack.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It maintains the command stack used in
connection with command recall.

Author: J.P. Terlouw
*/

#include "stddef.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include "string.h"
#include "alloc.h"
#include "filecontents.h"
#include "cmdstack.h"

#define NHIST 1000
#define increment(inx,direction) ((inx+NHIST+direction)%NHIST)

static char *history[NHIST];           /* circular buffer of command pointers */
static char *mstring=NULL;             /* match string */
static int  nmstring;                  /* length of match string */
static int  current=0;                 /* index of "current" command */
static int  next=0;                    /* index of next new command */


/* ========================================================================== */
/*                              StoreCmd                                      */
/* -------------------------------------------------------------------------- */
/*  StoreCmd() stores a command in the command stack. It will delete the
 *  oldest entry if necessary.
 */
extern void StoreCmd(char *cmd)
{
   int i=increment(next,CMD_BACKWARD);

   if (!history[i] || strcmp(history[i],cmd)) {
      if (history[next]) Delete(history[next]);
      history[next] = Malloc(strlen(cmd)+1);
      strcpy(history[next],cmd);
      next = increment(next,CMD_FORWARD);
   }
   ResetCmd();
}
/* ========================================================================== */
/*                               RecallCmd                                    */
/* -------------------------------------------------------------------------- */
/*  RecallCmd() returns the next entry of the command stack in the specified
 *  direction (CMD_FORWARD or CMD_BACKWARD). If there is no next entry in
 *  backward direction, NULL is returned; in forward direction the current
 *  value of the match string is returned, or an empty string if there is
 *  no match string.
 *  The argument 'match' sets the match string; this and subsequent calls
 *  with match==NULL will cause only matching entries to be returned.
 */
extern char *RecallCmd(char *match, int direction)
{
   int i;

   if (match) {
      if (mstring) Delete(mstring);
      nmstring = strlen(match);
      mstring = Malloc(nmstring+1);
      strcpy(mstring,match);
   }

   if (!(current==next && direction==CMD_FORWARD)) {
      for (i=increment(current,direction);
           history[i] && i != next; i=increment(i,direction)) {
         if (!mstring || !strncmp(mstring,history[i],nmstring)) {
            current = i;
            return history[i];
         }
      }
   }
   if (direction==CMD_FORWARD) {
      current = next;
      if (mstring) return mstring; else return "";
   }
   return NULL;
}

/* ========================================================================== */
/*                              ResetCmd                                      */
/* -------------------------------------------------------------------------- */
/*  ResetCmd() resets the current pointer and clears any match string.
 */
extern void ResetCmd(void)
{
      current = next;
      if (mstring) Delete(mstring);
}

/* ========================================================================== */
/*                              InitCmd                                       */
/* -------------------------------------------------------------------------- */
/*  InitCmd() reads any command file into the command stack.
 */
extern void InitCmd(void)
{
   char *cmdfile, *cmd;
   int  fsize, i;

   cmdfile = FileContents("HERMES.cmd",&fsize);
   if (cmdfile) {
      cmd = cmdfile;
      for (i=0; i<fsize; i++) {
         if (cmdfile[i]=='\n') {
            cmdfile[i] = '\0';
            if (*cmd) StoreCmd(cmd);
            cmd = cmdfile + i +1;
         }
      }
      Free(cmdfile);
   }
}

/* ========================================================================== */
/*                              FinisCmd                                      */
/* -------------------------------------------------------------------------- */
/*  FinisCmd() writes the contents of the command stack to the Hermes
 *  command file.
 */
extern void FinisCmd(void)
{
   int fd;
   char *cmd=NULL;

   fd = open("HERMES.cmd", O_RDWR|O_CREAT, 0666);
   if (fd>0) {
      (void)ftruncate(fd,0);
      ResetCmd();
      for (;;) {
         char *tmp;
         tmp = RecallCmd(NULL,CMD_BACKWARD);
         if (!tmp) break;
         cmd = tmp;
      }
      while(cmd && *cmd) {
         write(fd, cmd, strlen(cmd));
         write(fd,"\n",1);
         cmd = RecallCmd(NULL,CMD_FORWARD);
      }
      close(fd);
   }
}
#<

#>            coamgr.c
/* coamgr.c
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It manages the Common Output Area of tHermes.
Author: J.P. Terlouw.
*/
#include "stdlib.h"
#include "stdio.h"
#include "stddef.h"
#include "ctype.h"
#include "string.h"

#include <sys/file.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef	O_CREAT
#include <fcntl.h>
#endif
#include <sys/time.h>
#if defined(__aix__)
#include <sys/select.h>
#endif
#include "gipsyc.h"
#include "prntract.h"
#include "findstring.h"
#include "thermescom.h"

#ifndef	L_XTND
#define	L_XTND	SEEK_END
#endif
#define HC "screen.prt"

static Window coa;                              /* current common output area */
static Window prv;                              /* previous page              */
static Window cpage, dpage;                     /* page number displays       */
static int fd;                                  /* screen file descriptor     */
static int lineno, pageno, current;
static bool pmode=FALSE;
static int  doscroll=0;                         /* scroll or jump pages       */

static void CPageNo(void);
static void DPageNo(int);

/* ========================================================================== */
/*                               InitCoa                                      */
/* -------------------------------------------------------------------------- */
/*  InitCoa initializes the Common Output Area: nx columns wide and ny
 *  columns high.
 */
extern void InitCoa(int ny, int nx)
{
   int length;
   int i;
   char buffer[LINLEN+1];

   fd = open("SCREEN.LOG",O_CREAT|O_RDWR|O_APPEND,0666);
   length = lseek(fd,0,L_XTND);
   lineno = length/LINLEN;
   (void)ftruncate(fd,lineno*LINLEN);
   pageno = lineno/ny;
   coa = CreateWindow(ny,nx);           /* Common Ouput Area */
   prv = CreateWindow(ny,nx);           /* previous page overlaps COA exactly */
   (void)lseek(fd,pageno*LINLEN*ny,L_SET);
   buffer[LINLEN] = 0;
   for (i=pageno*ny; i<lineno; i++) {
      (void)read(fd,buffer,LINLEN);
      PutLine(coa,i%ny,buffer);
   }
   MapWindow(coa,0,0);
   cpage = CreateWindow(1,4);
   MapWindow(cpage,ny+1,nx-5);
   dpage = CreateWindow(1,4);
   CPageNo();
}

/* ========================================================================== */
/*                               put_coa                                      */
/* -------------------------------------------------------------------------- */
/*  put_coa() writes a line of text to the COA.
 *  Arguments:  task  --  task status block pointer of issuing task;
 *              line  --  line of text.
 */
extern void put_coa(TaskStatus task, char *line)
{
   char buffer[LINLEN+1];
   int i;
   static int scroll=0;

   if (!(task->devmask & SCREEN)) return;
   if (!doscroll) {
      if (!(lineno%coa->ny)) {
         ClearWindow(coa);
         pageno = lineno/coa->ny;
         CPageNo();
      }
      PutLine(coa,lineno%coa->ny,line);
   } else {
      if (!(lineno%coa->ny)) {
         scroll = 1;
         pageno = lineno/coa->ny;
         CPageNo();
      }
      if (scroll) {
         ScrollWindow(coa,1);
         PutLine(coa,coa->ny-1,line);
      } else PutLine(coa,lineno%coa->ny,line);
   }
   if (current==pageno && prv->mapped) {
      PutLine(prv,lineno%coa->ny,line);
      RefreshWindow(prv);
   } else RefreshWindow(coa);
   for (i=0; i<LINLEN; i++)
      if (*line) buffer[i] = *(line++); else buffer[i] = ' ';
   write(fd,buffer,LINLEN);
   lineno++;
}

/* ========================================================================== */
/*                             SearchCoa                                      */
/* -------------------------------------------------------------------------- */
/*  SearchCoa() searches for 'string' in the COA; forward if 'direction' is +1;
 *  backward if it is -1.
 *  It returns zero if string was found, -1 if it was not found, or in case
 *  the search was interrupted, the value of the character typed.
 */
extern int SearchCoa(char *string, int direction)
{
   char *page;
   int  pagesize, pnum, rnum=0;
   fd_set readfds, writefds, exceptfds;
   struct timeval timeout;
   char *found;
   int  result=0;

   if (pmode) pnum = current; else pnum = pageno;
   pagesize = coa->ny*LINLEN;
   page = (char*)Malloc(pagesize+1);

   FD_ZERO(&readfds);
   FD_ZERO(&writefds);
   FD_ZERO(&exceptfds);
   for (pnum = pnum+direction; pnum>=0 && pnum<=pageno; pnum += direction) {
      if ((pnum%17)==9) DPageNo(pnum); /* keep user informed every 17th page */
      timeout.tv_sec = 0;
      timeout.tv_usec = 0;
      FD_SET(0,&readfds);
      if (select(1,(void*)&readfds,
          (void*)&writefds,(void*)&exceptfds,&timeout)==1) {
          char c;
          (void)read(0,&c,1);
          result = c;
          break;
      }
      rnum += direction ;
      (void)lseek(fd,pnum*pagesize,L_SET);
      *(page+read(fd,page,pagesize)) = 0;
      if ((found = findstring(page,page,string,0))) {
         break;
      }
   }
   free(page);
   if (found) {
      ShowPage(rnum,1);
      result = 0;
   } else {
      if (!result) result = -1;                           /* not interrupted */
      if (!pmode) UnmapWindow(dpage); else DPageNo(current);
   }
   return result;
}

/* ========================================================================== */
/*                               ShowPage                                     */
/* -------------------------------------------------------------------------- */
/*  ShowPage() moves to page 'newpage' if 'relative' is zero; if 'relative'
 *  is non-zero it moves to the current page plus or minus 'newpage' pages.
 */
extern void ShowPage(int newpage, int relative)
{
   int i;
   char buffer[LINLEN+1];

   if (!pmode) {
      current = pageno;
   }

   if (newpage) {
      if (relative) current += newpage; else current = newpage-1;
      pmode = !(current>pageno);
      if (current<0) current = 0;
   } else pmode = !pmode;

   if (pmode) {
      (void)lseek(fd,current*LINLEN*prv->ny,L_SET);
      buffer[LINLEN] = 0;
      for (i=0; i<prv->ny; i++) {
         if (read(fd,buffer,LINLEN)) PutLine(prv,i,buffer);
         else PutLine(prv,i," ");
      }
      if (prv->mapped) {
         RefreshWindow(prv);
      } else {
         MapWindow(prv,0,0);
         MapWindow(cpage,-1,-1);
      }
      DPageNo(current);
   } else {
      if (prv->mapped) {
         UnmapWindow(prv);
         UnmapWindow(dpage);
      }
   }
}

/* ========================================================================== */
/*                               PrintCoa                                     */
/* -------------------------------------------------------------------------- */
/*  PrintCoa() prints a range of pages from the COA.
 */
extern void PrintCoa(int pstart, int pend)
{
   int lstart, lend;
   int pf;
   int i;
   int width=LINLEN<coa->nx?LINLEN:coa->nx;
   int effwid;
   int pnum=(pmode?current:pageno)+1;
   char buffer[LINLEN+1];

   if (pstart>0 || pend>0) {
      if (pstart<=0) pstart = pnum;
      else if (pstart>pageno) pstart = pageno+1;
      if (pend<=0) pend = pnum;
      else if (pend>pageno) pend = pageno+1;
   } else {
      pstart = pnum-2;
      pend   = pnum;
      if (pstart<1) pstart = 1;
   }
   if (pstart>pend) {
      pstart ^= pend;      /* these statements magically swap pstart and pend */
      pend   ^= pstart;
      pstart ^= pend;
   }
   lstart = (pstart-1)*coa->ny;
   lend   = pend*coa->ny;
   lstart = lstart>lineno?lineno:(lstart<0?0:lstart);
   lend   = lend  >lineno?lineno:(lend  <0?0:lend  );
   pf = open(HC,O_RDWR|O_CREAT,0666);
   (void)lseek(fd,lstart*LINLEN,L_SET);
   sprintf(buffer,"Hermes screen pages %d through %d\n\n",pstart,pend);
   write(pf,buffer,strlen(buffer));
   for (i=lstart; i<lend; i++) {
      read(fd,buffer,LINLEN);
      for (effwid = width; effwid && (buffer[effwid-1]==' '); ) effwid--;
      buffer[effwid]='\n';
      write(pf,buffer,effwid+1);
   }
   close(pf);
   (void)PrintFile(HC); /* +++ add reporting the return code */
}
/* ========================================================================== */
/*                                 SetScroll                                  */
/* -------------------------------------------------------------------------- */
/*  SetScroll sets the scroll mode for screen output and returns the previous
 *  scroll mode;
 *  Zero: do not scroll; non-zero: scroll.
 */
extern int SetScroll(int mode)
{
   int previous=doscroll;

   doscroll = mode;
   return previous;
}

/* -------------------------------------------------------------------------- */
/*                                  CPageNo                                   */
/* -------------------------------------------------------------------------- */
/*  CPageNo() shows the current page number.
 */
static void CPageNo(void)
{
   char pnstring[5];

   if (pageno<9999)
      sprintf(pnstring,"%4d",pageno+1);
   else
      sprintf(pnstring,"*%3.3d",(pageno+1)%1000);
   PutString(cpage,0,0,pnstring);
   RefreshWindow(cpage);
}

/* -------------------------------------------------------------------------- */
/*                                 DPageNo                                    */
/* -------------------------------------------------------------------------- */
/*  DPageNo() displays the number of the page currently being displayed.
 */
static void DPageNo(int pageno)
{
   char pnstring[5];

   if (pageno<9999) 
      sprintf(pnstring,"%4d",pageno+1);
   else
      sprintf(pnstring,"*%3.3d",(pageno+1)%1000);
   PutString(dpage,0,0,pnstring);
   if (!dpage->mapped) MapWindow(dpage,coa->ny+1,coa->nx-11);
   else RefreshWindow(cpage);
}
#<

#>            contextmenu.c
/* contextmenu.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It handles the tHermes context settings menu.
Author: J.P. Terlouw
*/

#include "stddef.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "thermescom.h"
#include "taskmgr.h"
#include "webhelp.h"
#include "contextmenu.h"

static Window win=NULL;
static TaskStatus current=NULL;
static long kid, tid;                      /* keyboard and task callback id's */
static char *digits[]={"0","1","2","3","4"};
static char *omodes[]={"EXPERT","NORMAL","TEST  "};
static char *off_on[]={"OFF","ON "};
static char layout[]=
" ===== CONTEXT SETTINGS ===== "
"                              "
" ---------------------------- "
"                      press:  "
"   Error   level: *    (E)    "
"   Message level: *    (M)    "
"   Output mode: ****** (O)    "
"   Screen output: ***  (S)    "
"   Log file:      ***  (L)    "
"   Hide keywords: ***  (H)    "
" ---------------------------- "
"       QUIT FROM MENU: (Q)    "
"                              ";
static int XSIZE=30, YSIZE=13;                          /* layout dimensions  */
static int EX=18, MX=18, OX=16, SX=18, LX=18, HX=18;    /* variable positions */
static int EY= 4, MY= 5, OY= 6, SY= 7, LY= 8, HY=9;

static void deactivate(void);
static int  getchars(long,int,void*);
static void tasknotify(long,TaskStatus,void*);
static void updatewin(void);

/* ========================================================================== */
/*                               ContextMenu                                  */
/* -------------------------------------------------------------------------- */
/*  ContextMenu activates the context menu for the given task, or deactivates
 *  it when it is already active. If the given task is different from the one
 *  associated with the menu just deactivated, a new menu for the given task
 *  is activated.
 *  There can be only one instance of a context menu, though it is fairly
 *  easy to implement multiple instances.
 */
extern void ContextMenu(TaskStatus task)
{
   TaskStatus ctemp = current;
   extern int LINES, COLS;                   /* screen dimensions from Curses */

   if (current) deactivate();                /* already active? */
   if (!task || task==ctemp) return;         /* unspecified or same task? */

   current = task;
   if (!win) {
      win = CreateWindow(YSIZE,XSIZE);  /* shape of array "layout" */
      free(win->backup);                /* maintain own backup */
      win->private = FALSE;
      win->backup = layout;
      win->framed = TRUE;
   }
   PutLine(win,1," ");
   PutString(win,1,(win->nx-strlen(task->name))/2,task->name);
   MapWindow(win,LINES-YSIZE-2,COLS-XSIZE-10);
   PutCursor(win,10,28);
   updatewin();
   kid = InsertKeyboardProc(getchars,NULL);
   tid = InsertTaskProc(tasknotify,NULL);
}

/*
 * deactivate() removes the context menu.
 */
static void deactivate(void)
{
   UnmapWindow(win);
   RemoveKeyboardProc(kid);
   RemoveTaskProc(tid);
   current = NULL;
}

/*
 * keyboard handler getchars() processes the characters it receives.
 * Characters that are not understood (i.e. other than alphabetic or CTRL C)
 * are propagated to the next handler.
 */
static int getchars(long id, int c, void *arg)
{

   if (c==QRESCHED) return YRESCHED;                 /* support rescheduling */
   if (c==XRESCHED) {                                /* perform rescheduling */
      PutCursor(win,10,28);
      MapWindow(win,-1,-1);
      RefreshWindow(win);
      return 0;
   }

   if (c==WEBHELP) {
      if (WebHelp("*hermes/changecontext.html")) return c;
      return 0;
   }

   if (c==CTRL_C) c = 'Q';                      /* translate to Quit command */

   if (isalpha(c)) {
      switch (toupper(c)) {
         case 'E':  current->errlev = current->errlev%4+1; break;
         case 'M':  current->meslev = current->meslev%4+1; break;
         case 'O':  current->output_mode = (current->output_mode+1)%3; break;
         case 'S':  current->devmask ^= SCREEN; break;
         case 'L':  current->devmask ^= LOGFILE; break;
         case 'H':  current->unhide = !current->unhide; break;
         case 'Q':  deactivate(); return 0;
         default :  Beep(); return 0;
      }
      updatewin();
      return 0;
   } else return c;                     /* propagate to next keyboard handler */
}

/*
 * Task state change handler. If the task is the same as the task for
 * which the context menu is active, and if the task status indicates
 * that it is inactive, the menu is removed.
 */
static void tasknotify(long id, TaskStatus task, void *arg)
{
   if (task==current) {
      if (task->proc) updatewin(); else deactivate();
   }
}

/*
 * updatewin() updates the window layout to reflect any changes in the
 * task status.
 */
static void updatewin(void)
{
   PutString(win,EY,EX,digits[current->errlev]);
   PutString(win,MY,MX,digits[current->meslev]);
   PutString(win,OY,OX,omodes[current->output_mode]);
   PutString(win,SY,SX,off_on[(current->devmask&SCREEN)&&1]);
   PutString(win,LY,LX,off_on[(current->devmask&LOGFILE)&&1]);
   PutString(win,HY,HX,off_on[(!current->unhide)]);
   RefreshWindow(win);
}
#<

#>            do_request.c
/* do_request.c
                           COPYRIGHT (c) 1991, 2001
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands


              'do_request.dc3'
Function:      do_request

Purpose:       Process servant task's request.

File:          do_request.c

Author:        J.P. Terlouw

Use:           do_request(task);

                  TaskStatus task :  pointer to task status block containing the
                                     request to be executed.

               This function is used exclusively by Hermes.


Updates:       Jan 21, 1991, JPT: created document.

*/
#include "hermescom.h"

#include "ctype.h"
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
#include "dcdlog.h"
#include "dcdchar.h"
#include "dcdcharu.h"
#include "dcdcharl.h"
#include "dcdparse.h"
#include "dcderrstr.h"
#include "execpath.h"
#include "time.h"
#include "alloc.h"
#include "ucamgr.h"

#define  NOTIMPL reply_send(curstat,ERRNOTIMP);


TaskStatus NameToTask(char *name);
bool satisfy(TaskStatus, UserinpRequest);

static void   init    (TaskStatus,InitRequest);
static void   finis   (TaskStatus);
static void   cancel  (TaskStatus,CancelRequest);
static void   reject  (TaskStatus,RejectRequest);
static void   pause   (TaskStatus,PauseRequest);
static void   userinp (TaskStatus,UserinpRequest);
static void   deputy  (TaskStatus,DeputyRequest);
static void   xeq     (TaskStatus,XeqRequest);
static void   subst   (TaskStatus,SubstRequest);
static void   anyout  (TaskStatus,AnyoutRequest);
static void   canall  (TaskStatus);
static void   myname  (TaskStatus);
static void   allpar  (TaskStatus,AllparRequest);
static void   savepar (TaskStatus,SaveparRequest);
static void   error   (TaskStatus,ErrorRequest);
static void   abortt  (TaskStatus,AbortRequest);
static void   status  (TaskStatus,StatusRequest);
static void   wkey    (TaskStatus,WkeyRequest);
static void   type    (TaskStatus,TypeRequest);
static void   edtfile (TaskStatus,EditfileRequest);
static void   listctrl(TaskStatus,ListctrlRequest);
static void   decode  (TaskStatus,DecodeRequest);
static void   dcderr  (TaskStatus,DcderrRequest);
static void   notify  (TaskStatus,NotifyRequest);
static void   xeqcont (TaskStatus,XeqcontRequest);
static void   last    (TaskStatus,LastRequest);

static void   parproc (long,char*,void*);
static void   edtproc (long,char*,int,void*);

void do_request(TaskStatus curstat)
{
Request     curreq;

      curreq = (Request)&curstat->request;
      switch (curreq->code) {
         case INIT    :   init(curstat,(InitRequest)curreq); break;
         case FINIS   :   finis(curstat); break;
         case CANCEL  :   cancel(curstat,(CancelRequest)curreq); break;
         case REJECT  :   reject(curstat,(RejectRequest)curreq); break;
         case PAUSE   :   pause(curstat,(PauseRequest)curreq); break;
         case USERINP :   userinp(curstat,(UserinpRequest)curreq); break;
         case DEPUTY  :   deputy(curstat,(DeputyRequest)curreq); break;
         case XEQ     :   xeq(curstat,(XeqRequest)curreq); break;
         case SUBST   :   subst(curstat,(SubstRequest)curreq); break;
         case ANYOUT  :   anyout(curstat,(AnyoutRequest)curreq); break;
         case CANALL  :   canall(curstat); break;
         case MYNAME  :   myname(curstat); break;
         case ALLPAR  :   allpar(curstat,(AllparRequest)curreq); break;
         case SAVEPAR :   savepar(curstat,(SaveparRequest)curreq); break;
         case ERROR   :   error(curstat,(ErrorRequest)curreq); break;
         case ABORT   :   abortt(curstat,(AbortRequest)curreq); break;
         case STATUS  :   status(curstat,(StatusRequest)curreq); break;
         case WKEY    :   wkey(curstat,(WkeyRequest)curreq); break;
         case XEQXIT  :   NOTIMPL; break;
         case TYPE    :   type(curstat,(TypeRequest)curreq); break;
         case EDITFILE:   edtfile(curstat,(EditfileRequest)curreq); break;
         case LISTCTRL:   listctrl(curstat,(ListctrlRequest)curreq); break;
         case DECODE  :   decode(curstat,(DecodeRequest)curreq); break;
         case DCDERR  :   dcderr(curstat,(DcderrRequest)curreq); break;
         case NOTIFY  :   notify(curstat,(NotifyRequest)curreq); break;
         case XEQCONT :   xeqcont(curstat,(XeqcontRequest)curreq); break;
         case LAST    :   last(curstat,(LastRequest)curreq); break;
         default      :   NOTIMPL;
      }
}

static void init(TaskStatus curstat, InitRequest request)
{
   char line[LINLEN];
   int i;
   time_t now;
   
   if (request->version == VERSION) {
       curstat->state = RUNNING;
      (*curstat->isproc)(curstat);       /* takes also care of screen message */
      /* tell task which directory to use: */
      if (strlen(curstat->workdir))
         reply_put(curstat,curstat->workdir,strlen(curstat->workdir));
      reply_send(curstat,0);
   } else {
      curstat->state = FATAL;
      reply_send(curstat,BADVERSION);
      put_psa(curstat,"BAD VERSION; task should be re-compiled" ,S_TERM);
      /* screen message delayed until process exits */
   }
   for (i=0; i<LINLEN; i++) line[i] = ' ';
   sprintf(&line[LINLEN-40],"(start %s)",curstat->proc->name);
   line[strlen(line)]=' ';
   now = time(NULL);
   (void)strftime(&line[LINLEN-21],20,"%d/%m/%y %H:%M:%S",(localtime(&now)));
   put_log(curstat,line);                                /* log date and time */
   put_coa(curstat,line+LINLEN-40-42);
}

static void finis(TaskStatus curstat)
{
   curstat->state = FINISHED;
   reply_send(curstat,0);
   /* screen message delayed until process exits */
}

static void cancel(TaskStatus curstat, CancelRequest request)
{
   StringUp(request->key);
   sbstte(curstat,request->key);
   cancel_par(curstat,request->key);
   reply_send(curstat,0);
}

static void reject(TaskStatus curstat, RejectRequest request)
{
   StringUp(request->key);
   sbstte(curstat,request->key);
   err_uca(curstat,request->message);
   reject_par(curstat,request->key);
   reply_send(curstat,0);
}

static void pause(TaskStatus curstat, PauseRequest request)
{
   char paumsg[]=" - PAUSING";
   char message[MSGLEN+sizeof(paumsg)];
   (void)strcpy(message,request->message);
   (void)strcat(message,paumsg);
   put_psa(curstat,message,S_PAUSE);
   suspend(curstat);
   curstat->state = PAUSING;
   (*curstat->isproc)(curstat);
   reply_send(curstat,0); /* success code will be read by task after resuming */
}

static void userinp(TaskStatus curstat, UserinpRequest request)
{
   int bytes_req;

   switch (request->type) {
      case USERINT: bytes_req = sizeof(fint)*request->number; break;
      case USERLOG: bytes_req = sizeof(bool)*request->number; break;
      case USERREAL: bytes_req = sizeof(float)*request->number; break;
      case USERDBLE: bytes_req = sizeof(double)*request->number; break;
      case USERCHAR: bytes_req = request->width*request->number; break;
      case USERCHARU: bytes_req = request->width*request->number; break;
      case USERCHARL: bytes_req = request->width*request->number; break;
      case USERTEXT: bytes_req = request->number; break;
   }
                                           /* allocate reply buffer and check */
   if (reply_alloc(curstat,bytes_req)) {
      reply_send(curstat,-1);
      return;
   }
                                           /* request for special information */
   if ((request->level) < 0 ) {
      int  nc;
      char *source;
      
      if (request->type != USERTEXT) {
         reply_send(curstat,-3);
         return;
      }
      StringUp(request->key);
      if (!strcmp("UCAMESSAGE=",request->key)) {
         source = curstat->ucamessage;
      } else if (!strcmp("REJECTED=",request->key)) {
         source = curstat->rejected;
      } else if (!strcmp("LSTMESSAGE=",request->key)) {
         source = curstat->lstmessage;
      } else {
         reply_send(curstat,-4);
         return;
      }
      if (source) {
         for (nc=0; nc<bytes_req && source[nc]; nc++) {
            curstat->reply[nc] = source[nc];
         }
         curstat->reply_in = nc;
         reply_send(curstat,0);
         return;
      } else {
         reply_send(curstat,-5);
         return;
      }
   }
                                                  /* un-hide hidden keyword? */
   if (curstat->unhide && (request->level&0x00000003)==2) {
      request->level = (request->level&0xFFFFFFFC)|1;
   }

   StringUp(request->key);
   sbstte(curstat,request->key);
   if (!satisfy(curstat,request)) {
      if (curstat->proc->fd_notify) {
         reply_send(curstat,-2);                    /* report error condition */
      } else {
         curstat->state = WAITINP;
         put_psa(curstat,request->message,S_USERINP);
         curstat->parcb = InsertParProc(parproc,(void*)curstat);
         (*curstat->isproc)(curstat);
      }
   }
}

static void decode(TaskStatus curstat, DecodeRequest request)
{
   int  bytes_req, error=0, typlen;
   fint length;

   switch (request->type) {
      case USERINT:  typlen = sizeof(fint);   break;
      case USERREAL: typlen = sizeof(float);  break;
      case USERDBLE: typlen = sizeof(double); break;
   }
   bytes_req = typlen*request->number;
                                           /* allocate reply buffer and check */
   if (reply_alloc(curstat,bytes_req)) {
      reply_send(curstat,-999);
      return;
   }
   switch (request->type) {
      case USERINT:  {
         length = DecodeFint(request->expr,(fint*)curstat->reply, 
                             request->number, &error);
         break;
      }
      case USERREAL: {
         length = DecodeReal(request->expr,(float*)curstat->reply, 
                             request->number, &error);
         break;
      }

      case USERDBLE:  {
         length = DecodeDouble(request->expr,(double*)curstat->reply, 
                               request->number, &error);
         break;
      }

   }
   if (error) {
      reply_send(curstat,error);
   } else {
      curstat->reply_in = length*typlen;
      reply_send(curstat,length);
   }
   return;
}

static void dcderr(TaskStatus curstat, DcderrRequest request)
{
   char *message;
    
   message = dcderrstr(request->error);
   reply_put(curstat, message , strlen(message));
   reply_send(curstat,0);
}

static void notify(TaskStatus curstat, NotifyRequest request)
{
   char *name;
   
   if (request->mode) {
      name = NotifyName();
      reply_put(curstat, name, strlen(name));
      reply_send(curstat,0);
      (void)StartNotify(curstat);
   } else {
      reply_send(curstat,0);
      StopNotify(curstat);
   }
}

static void deputy(TaskStatus curstat, DeputyRequest request)
{
   char *task=request->taskname;
   char *tail;
   char *execname;

   for (tail=task+strlen(task)-1; tail>task; tail--) {
      if (*tail==' ') *tail=0; else break;
   }                                             /* remove trailing blanks */
   for (tail=task+strlen(task)-1; tail>=task; tail--) {
      *tail = tolower(*tail);
   }
   execname = FindExec(task);
   if (!execname) {
      reply_send(curstat,NOTPRESENT);
   } else if (!starttask(curstat,execname)) {
      reply_send(curstat,MAXDEPEXC);
   }
   /* reply_send will eventually be called by routine rundown in taskmgr.c */
}

static void xeq(TaskStatus curstat,XeqRequest request)
{
   long success;
   success = (long)Exec(curstat,request->command);
   if (!success) reply_send(curstat,-1);
   if (success==-1) reply_send(curstat,1);  /* *negative* success code! */
   /* reply_send will eventually be called by impl. specific clean-up */
}

static void xeqcont(TaskStatus curstat,XeqcontRequest request)
{
   extern TaskStatus Template;
   TaskStatus newtask;
   int  i;
   extern int NTASK;
   char keymsg[KEYLEN+1];
      
   newtask = Exec(Template, request->command);
   if (newtask && (long)newtask != -1) {
      reply_send(curstat,0);
      if (request->key[0]!='*') {
         /* keyword not an asterisk: set up caller-callee relation */
         newtask->xcaller = curstat;
         strcpy(newtask->xkey, request->key);
         for (i=0; i<NTASK-1; i++) {
            if (!curstat->xcallee[i]) {
               curstat->xcallee[i] = newtask;
               break;
            }
         }
         sprintf(keymsg, "N_%s", request->key);
         insert_par(curstat, keymsg, newtask->name, 0);
      }
   } else {
      reply_send(curstat,-1);
   }
}

static void subst(TaskStatus curstat,SubstRequest request)
{
   char key[KEYLEN], equ[KEYLEN];
   char *string=request->string;
   int  i;

   StringUp(string);
   for (;;) {
      for (i=0; (i<KEYLEN)&&(*string); i++) {
         key[i] = *(string++);
         if (key[i]=='=') {
            key[++i] = 0;
            i=-1;
            break;
         }
      }
      if (i!=-1) {
         reply_send(curstat,0);
         return;
      }
      for (i=0; (i<KEYLEN)&&(*string); i++) {
         equ[i] = *(string++);
         if (equ[i]=='=') {
            equ[++i] = 0;
            i=-1;
            break;
         }
      }
      if (i!=-1) {
         reply_send(curstat,SUBSTODD);
         return;
      }
      if (sbspec(curstat,key,equ)) {
         reply_send(curstat,SUBSTFULL);
         return;
      }
   }
}

static void anyout(TaskStatus curstat, AnyoutRequest request)
{
   if (request->devmask == 0) request->devmask = SCREEN | LOGFILE;
   if ( (request->devmask & SCREEN) ||
        ((request->devmask & NOEXPERT) && curstat->output_mode >= NORMAL) ||
        ((request->devmask & SCREENTEST) && curstat->output_mode >= TEST) )
        put_coa(curstat,request->line);
   if ( request->devmask & LOGFILE ) put_log(curstat,request->line);
   if (!(request->devmask&LOCALDEV)) reply_send(curstat,0);
}

static void canall(TaskStatus curstat)
{
   curstat->keep = 0;
   init_par(curstat);
   reply_send(curstat,0);
}

static void myname(TaskStatus curstat)
{
   char name[NAMLEN];
   int  i;

   for (i=0; i<NAMLEN; i++) {
      name[i]=toupper(curstat->name[i]);
   }
   reply_put(curstat, name, strlen(name));
   reply_send(curstat,0);
}

static void allpar(TaskStatus curstat, AllparRequest request)
{
   int  length;
    
   if (reply_alloc(curstat,request->length+1)) {
      reply_send(curstat,-1);
      return;
   }
   if (request->mode) {
      if (!curstat->keyfile) {
         reply_send(curstat,-2);
         return;
      }
   } else {
      if (curstat->keyfile) {
         fclose((FILE*)curstat->keyfile);
      }
      if (write_par(curstat->name,"keys.tmp")) {
         reply_send(curstat,-3);
         return;
      }
      if (!(curstat->keyfile = fopen("keys.tmp","r"))) {
         reply_send(curstat,-4);
         return;
      }
      remove("keys.tmp");
   }
   (void)fgets(curstat->reply,request->length+1,(FILE*)curstat->keyfile);
   if (feof((FILE*)curstat->keyfile)) {
      fclose((FILE*)curstat->keyfile);
      curstat->keyfile = NULL;
      reply_send(curstat,0);
      return;
   }
   length = strlen(curstat->reply);
   if (curstat->reply[length-2]=='\n') length--;
   curstat->reply_in = length;
   reply_send(curstat,length);
}

static void savepar(TaskStatus curstat, SaveparRequest request)
{
   reply_send(curstat,write_par(curstat->name,request->filename));
}

static void error(TaskStatus curstat, ErrorRequest request)
{
   char fatalmsg[]=" - FATAL";
   char warnmsg[] =":WARNING";
   char errormsg[]=" - ERROR";
   char message[MSGLEN+sizeof(fatalmsg)]; /* use largest suffix in sizeof! */
   int  result=0;

   (void)strcpy(message,request->message);
   if (request->level >= curstat->errlev) {
      (void)strcat(message,fatalmsg);
      put_psa(curstat,message,S_TERM);
      result = ERRLEVEXC;
      curstat->state = FATAL;
   } else if (request->level >= curstat->meslev) {
      if (request->level==1) (void)strcat(message,warnmsg);
      else (void)strcat(message,errormsg);
      put_psa(curstat,message,S_TERM);
   }
   reply_send(curstat,result);
}

static void abortt(TaskStatus curstat, AbortRequest request)
{
   TaskStatus target=NameToTask(request->taskname);
   int result=-1;
   if (target) {
      target->state = ABORTED;
      aborttask(target);
      result = 0;
   }
   reply_send(curstat,result);
}

static void status(TaskStatus curstat, StatusRequest request)
{
   put_psa(curstat,request->message,S_STATUS);
   reply_send(curstat,0);
}

static void edtfile(TaskStatus curstat, EditfileRequest request)
{
   int result;

   put_psa(curstat,request->message,S_USERINP);
   if (!(result=EditFile(request->filename))) {
      curstat->state = WAITEDIT;
      curstat->edtcb = InsertEditProc(edtproc,(void*)curstat);
      (*curstat->isproc)(curstat);
   } else reply_send(curstat,result);
}

static void listctrl(TaskStatus curstat, ListctrlRequest request)
{
   TaskStatus current=curstat;
   int result=curstat->devmask&0x3;
    
   if (!(request->mask&~0x3)) {
      while (current) {
         current->devmask = (current->devmask&~0x3)|request->mask;
         TaskChanged(current);
         current = current->caller;
      }
   }
   reply_send(curstat,result);
}
   

static void wkey(TaskStatus curstat, WkeyRequest request)
{
   TaskStatus target;
   char *key, *value;
   long cmdid;
   int source=0, parse_ok;

   parse_ok = ParseCmd(request->command);
   cmdid = CmdGetInfo();
   if (!parse_ok) {
      target = NameToTask(CmdTaskName(cmdid));
      if (target) {
         if (target->proc->fd_notify) {
            /* only force caller to wait until keywords are read
             * if target is event-driven.
             */
            source = curstat->proc->pid;
         }
         curstat->keydest = target;
      } else parse_ok = -1;
   } else if (parse_ok == NOTSKNAME) {
      target = curstat;
      parse_ok = 0;
   }
   if (!parse_ok) {
      while (1) {
         CmdParam(cmdid,&key,&value);
         if (!key) break;
         insert_par(target, key, value, source);
         if (source) curstat->waitkeyread++;
      }
   }
   CmdDelete(cmdid);
   if (!curstat->waitkeyread) reply_send(curstat,0);
}

static void type(TaskStatus curstat, TypeRequest request)
{
   int i;
    
   for (i=0; i<TYPLEN && request->string[i]; i++) {
      UcaType(request->string[i]);
   }
   reply_send(curstat,0);
}

static void last(TaskStatus curstat, LastRequest request)
{
   char line[LINLEN];
   int i;
   time_t now;
   
   switch (curstat->state) {
      case FINISHED:
      case FATAL:
      case ABORTED:
      case NODEFAULT: break;

      case WAITRUN: {
         curstat->state = STARTFAIL;      /* abortion     */
         break;
      }

      default: {
         curstat->state = CRASHED;        /* sudden death */
      }
   }
   for (i=0; i<LINLEN; i++) line[i] = ' ';
   sprintf(&line[LINLEN-40],"(end   %s)",curstat->proc->name);
   line[strlen(line)]=' ';
   now = time(NULL);
   (void)strftime(&line[LINLEN-21],20,"%d/%m/%y %H:%M:%S",(localtime(&now)));
   put_log(curstat,line);
   put_coa(curstat,line+LINLEN-40-42);
   rundown(curstat);
}


extern bool satisfy(TaskStatus status, UserinpRequest request)
{
   char  *value, *vptr, *valbuf;
   int   length, typlen, count, level, exact;
   fint  error;
   fchar cvalue, cresult;

   level  = request->level&3;
   exact  = request->level&4;
   vptr = get_par(status,request->key);
   if (vptr) {
      int i;
      if (level==3) {                                     /* NO-READ request */
         reply_send(status,0);
         return 1;
      }
      valbuf = Malloc(strlen(vptr)+1);
      value = valbuf;
      for (i=0; vptr[i]; i++)
         if (vptr[i] != '`') *(value++) = vptr[i];     /* remove back-quotes */
      *value = '\0';
      value = valbuf;
      length=strlen(value);
   } else length=-1;
   if (length<0) {                                         /* key absent     */
      if (level == 2) {
         reply_send(status,0);
         err_uca(status,NULL);                    /* reset any error message */
         if (status->state == WAITINP) {
            status->state = RUNNING;
            status->prevstat = WAITINP;
            (*status->isproc)(status);
         }
         return 1;
      } else {
         return 0;
      }
   }
   if (length == 0) {                               /* key present but empty */
      if (level>0) {
         reply_send(status,0);
         err_uca(status,NULL);                    /* reset any error message */
         if (status->state == WAITINP) {
            status->state = RUNNING;
            status->prevstat = WAITINP;
            (*status->isproc)(status);
         }
         Free(valbuf);
         return 1;
      } else {
         err_uca(status,"NO DEFAULT");
         NotifyCaller(status, 3);
         Free(valbuf);
         return 0;
      }
   }

   cvalue.a = value;
   cvalue.l = length;
   switch (request->type) {
      case USERINT   :
           typlen = sizeof(fint);
           length = DecodeFint(value, (fint*)status->reply, (int)request->number,
                             (int*)&error);
           break;

      case USERLOG   :
           typlen = sizeof(bool);
           length = dcdlog_c(cvalue, (bool*)status->reply, (fint*)&request->number,
                             &error);
           break;

      case USERREAL  :
           typlen = sizeof(float);
           length = DecodeReal(value, (float*)status->reply, (int)request->number,
                              (int*)&error);
           break;

      case USERDBLE  :
           typlen = sizeof(double);
           length = DecodeDouble(value, (double*)status->reply, (int)request->number,
                              (int*)&error);
           break;

      case USERCHAR  :
           typlen = request->width;
           cresult.a = status->reply;
           cresult.l = typlen;
           length = dcdchar_c(cvalue, cresult, (fint*)&request->number, &error);
           break;

      case USERCHARU :
           typlen = request->width;
           cresult.a = status->reply;
           cresult.l = typlen;
           length = dcdcharu_c(cvalue, cresult, (fint*)&request->number,&error);
           break;

      case USERCHARL :
           typlen = request->width;
           cresult.a = status->reply;
           cresult.l = typlen;
           length = dcdcharl_c(cvalue, cresult, (fint*)&request->number,&error);
           break;

      case USERTEXT  :
           typlen = 1;
           for (count=0; count<length && count<request->number; count++)
              status->reply[count] = value[count];
           error = 0;
           break;
   }
   if (error<0) {                                   /* error in decoding? */
      char errmsg[SW];
      reject_par(status,request->key);
      status->rejected = vptr;
      strcpy(errmsg,"BAD INPUT: ");
      strncat(errmsg,dcderrstr(error),80-strlen(errmsg)-1);
      err_uca(status,errmsg);
      NotifyCaller(status, 3);
      Free(valbuf);
      return 0;
   }
   if (length==0) {                                 /* interpreted as empty? */
      if (level>0) {
         reply_send(status,0);
         err_uca(status,NULL);                     /* reset any error message */
         if (status->state == WAITINP) {
            status->state = RUNNING;
            status->prevstat = WAITINP;
            (*status->isproc)(status);
         }
         Free(valbuf);
         return 1;
      } else {
         err_uca(status,"NO DEFAULT");
         NotifyCaller(status, 3);
         Free(valbuf);
         return 0;
      }
   }
   if (exact && length != request->number) {
      char message[SW];
      sprintf(message,"EXACTLY %d ITEMS REQUIRED", request->number);
      reject_par(status,request->key);
      status->rejected = vptr;
      err_uca(status,message);
      Free(valbuf);
      return 0;
   }
   status->reply_in = length*typlen;
   reply_send(status,0);
   err_uca(status,NULL);                           /* reset any error message */
   if (status->state == WAITINP) {
      status->state = RUNNING;
      status->prevstat = WAITINP;
      (*status->isproc)(status);
   }
   Free(valbuf);
   return 1;
}

/*
 *  parameter change callback
 */
static void parproc(long id, char *taskname, void *arg)
{
   TaskStatus task=(TaskStatus)arg;

   if (!strcmp(taskname,task->name) && task->state == WAITINP) {
      if (satisfy(task,(UserinpRequest)&task->request)) {
         RemoveParProc(id);
         task->parcb = 0;
      }
   }
}

/*
 *  edit completion callback
 */
static void edtproc(long id, char *filename, int status, void *arg)
{
   TaskStatus task=(TaskStatus)arg;

   if (task->state == WAITEDIT) {
      if (!strcmp(filename,((EditfileRequest)&task->request)->filename)) {
         RemoveEditProc(id);
         task->edtcb = 0;
         if (status) {
            if (status==EDT_CANCEL) reply_send(task,-1);
            else if (status==EDT_STARTFAIL) reply_send(task,-2);
            else if (status==EDT_NOEDITOR) reply_send(task,ERRNOTIMP);
            else reply_send(task,-3);
         } else reply_send(task,0);
         task->state = RUNNING;
         (*task->isproc)(task);
      }
   }
}
#<

#>            dcderrstr.c
/* dcderrstr.c
                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to the Hermes user input parser.
It translates an error code the associated error message. 

Author: J.P. Terlouw
*/

#include "gipsyc.h"
#include "stdio.h"
#include "gds_errstr.h"
#include "dcderrstr.h"
#include "dcdcom.h"

#define GDSMLEN 80

typedef struct {
   int  code;
   char *message;
} err_entry;

static err_entry list[]={
   { VARCASS ,  "attempt to assign to constant"},
   { VARLONG ,  "variable name too long"},   
   { VARFULL ,  "variable table full"},   
   { BADFUN  ,  "unknown function"},
   { BADPSYN ,  "syntax error"},
   { BADSSYN ,  "syntax  error"},
   { BADINCR ,  "bad list increment"},
   { BADNARG ,  "incorrect number of arguments"},
   { ERARITH ,  "arithmetic error"},
   { BADLSTL ,  "unequal list lengths"},
   { BUFOVF  ,  "output buffer overflow"},
   { FLOVUF  ,  "invalid number"},
   { BADELEM ,  "bad list element"},
   { BADOPER ,  "bad operand"},
   { BADARG  ,  "bad argument type"},
   { BADBOX  ,  "bad box argument"},
   { BADISUB ,  "only one subset allowed"},
   { BADCTYP ,  "bad table column type"},
   { TMPOVF  ,  "temporaries table overflow"},
   { BADCONV ,  "conversion error"} ,
   { BADRANG ,  "bad row range"},
   { BADFILE ,  "cannot open file"},
   { BADCOL  ,  "bad column"},
   { TOOMANY ,  "too many items requested"},
   { BADINDX ,  "bad list index"},
   { BADSUB  ,  " bad subset specification"}

};

static char unknown[]="unknown error";
static char leverr[50];
static int n_errors=sizeof(list)/sizeof(err_entry);
static char  gdsmess[GDSMLEN+1];
static fchar gdschar={gdsmess,GDSMLEN};

/* ========================================================================== */
/*                                 dcderrstr                                  */
/* -------------------------------------------------------------------------- */
/*  dcderrstr() returns a pointer to a character string containing the
 *  error message associated with the specified code.
 *  Depending on the kind of error, codes have different ranges.
 *
 *         0 > code > -1000     -- error detected by parser
 *     -1000 > code > -2000     -- error detected by GDS
 *     -2000 > code > -3000     -- error detected by dcdset()
 */
extern char* dcderrstr(int code)
{
   int i;
   
   if (code<-2000) {
      sprintf(leverr,"subset decode error %d", code+2000);
      return leverr;
   } else if (code<-1000) {
      fint gdscode=code+1000;
      gds_errstr_c( gdschar, &gdscode );
      gdsmess[GDSMLEN] = '\0';
      for (i=GDSMLEN-1; i; i--) 
         if (gdsmess[i]==' ') gdsmess[i] = '\0'; else break;
      return gdsmess;
   } else {
      for (i=0; i<n_errors; i++) 
         if (list[i].code==code) return list[i].message;
      return unknown;
   }
}
      
#<

#>            dcdfile.c
/* dcdfile.c
                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands


Routine to be called by the Hermes input parser to obtain information
from text files.
   
Author: J.P. Terlouw

*/

#include "gipsyc.h"
#include "stdlib.h"
#include "string.h"
#include "stdio.h"
#include "absname.h"
#include "dcdcom.h"
#include "varmgr.h"
#include "dcdrange.h"
#include "dcdfile.h"
#include "dcdparse.h"

#define COMMENT   '!'

#define LINALLOC  10000
#define LINLEN     1000

static char *GetLine(char *line, int n, FILE *stream);

/* ========================================================================== */
/*                                 dcd_file                                   */
/* -------------------------------------------------------------------------- */
/*  dcd_file() reads data from a text file.
 *
 *  Arguments:
 *               filename  -  TEXT structure with the file name;
 *               column    -  DOUBLE structure with the column number;
 *               rows      -  TEXT structure with the range.
 *
 *  Returns:     DOUBLE or LIST structure with the number(s) read from file.
 *               NONE-type structure if error occurred.
 */
extern Value dcd_file(Value filename, Value column, Value rows) 
{
   Value result={NONE, 0, 0.0, NULL};
   static double blankvalue=0.0;
   int   i, rtype, start, end, row, col, comments=0, nrows=0, maxlines;
   double *values;
   char   line[LINLEN+1];
   FILE  *textfile;
   
   if (!blankvalue) blankvalue = GetVar("BLANK").value;
                                                           /* column number */
   col = NINT(column.value)-1;
   if (column.type != DOUBLE || col<0) {
      FreeVar(&filename);
      FreeVar(&column);
      FreeVar(&rows);
      dcderror(BADCOL);
      return result;
   }
   FreeVar(&column);
                                                           /* row number(s) */
   start = 1; end = -1;
   rtype = dcdrange((char*)rows.values,&start,&end);
   FreeVar(&rows);
   if (rtype<0 || (end>0 && start>end) ) {
      FreeVar(&filename);
      dcderror(BADRANG);
      return result;
   }
                                                           /* open file    */
   textfile = fopen(absname(NULL,(char*)filename.values),"r");
   FreeVar(&filename);
   if (!textfile) {
      dcderror(BADFILE);
      return result;
   }
                                                           /* allocate buffer */
   values = (double *)malloc(LINALLOC*sizeof(double));
   maxlines = LINALLOC;
                                                           /* read file    */
   for (row=1; row<start; row++) (void)GetLine(line,LINLEN,textfile);
   for (row=start; end<0 || row<=end; row++) {
      char *txtnum;
      (void)GetLine(line,LINLEN,textfile);
      if (feof(textfile)) break;
      if (*line=='!' || *line=='#' || *line=='\0') {
         comments++;
         continue;
      }
      if (nrows>=maxlines) {
         double *newvalues;
         newvalues = realloc(values,(maxlines+LINALLOC)*sizeof(double));
         if (!newvalues) {
            free(values);
            dcderror(BUFOVF);
            return result;
         }
         values = newvalues;
         maxlines += LINALLOC;
      }
      values[nrows] = blankvalue;
      txtnum = strtok(line," \011");
      for (i=0; i<col && txtnum; i++) txtnum = strtok(NULL," \011");
      if (txtnum) {
         char  *numend;
         double number = strtod(txtnum, &numend);
         if (numend>txtnum) values[nrows] = number;
      }
      nrows++;
   }
   fclose(textfile);
                                                           /* results      */
   if (!nrows) {                              /* no rows found -- error */
      dcderror(BADRANG);
   } else if (nrows==1) {                     /* one value -- DOUBLE    */
      result.type = DOUBLE;
      result.value = values[0];
   } else if (end>0 && (nrows+comments)<(end-start+1)) {
      dcderror(TOOMANY);
   } else {
      result = NewList(nrows);
      if (result.type != LIST) return result; /* list creation failed   */
      for (i=0; i<nrows; i++) result.values[i] = values[i];
   }
   free(values);
   return result;
}

static char *GetLine(char *line, int n, FILE *stream)
{
   char *result;
   int  i=0;
    
   result = fgets(line, n, stream);
   if (result) {
      while (*line) {
         if (*line=='\n' || (i>0 && (*line=='!' || *line=='#'))) {
            *line = '\0';
            break;
         }
         line++;
         i++;
      }
      for (i=0; result[i]; i++) if (result[i] != ' ') break;
      if (!result[i]) *result = '\0';
   }
   return result;
}
#<

#>            dcdgds.c
/* dcdgds.c
                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands


  Collection of routines to be called by the Hermes input parser to
  obtain information from GDS sets.
   
  Author: J.P. Terlouw

*/

#include <ctype.h>

#include "gipsyc.h"
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "setfblank.h"
#include "setdblank.h"
#include "dcdcom.h"
#include "absname.h"
#include "dcdsetdef.h"
#include "gdsparams.h"
#include "gds_lock.h"
#include "gds_unlock.h"
#include "gdsd_rdble.h"
#include "gdsc_ndims.h"
#include "dcdpos.h"
#include "gdsc_fill.h"
#include "gdsi_read.h"
#include "gdsa_colinq.h"
#include "gdsa_rcint.h"
#include "gdsa_rcreal.h"
#include "gdsa_rcdble.h"
#include "gdst_immediate.h"
#include "varmgr.h"
#include "dcdrange.h"
#include "dcdgds.h"
#include "dcdparse.h"

static void initialize(void);

static int     initialized=0;
static float   blank;
static double  dblank;
static double  blankvalue;

/* ========================================================================== */
/*                                 dcd_descr                                  */
/* -------------------------------------------------------------------------- */
/*  dcd_descr() reads data from a set's descriptor.
 *
 *  Arguments:
 *               setname  -  TEXT structure with the set name;
 *               lstring  -  TEXT structure with a subset specification OR
 *                           NONE-type structure if subset not specified;
 *               keyword  -  TEXT structure with the descriptor item name.
 *
 *  Returns:     DOUBLE or LIST structure with the descriptor values.
 *               NONE-type structure if error occurred.
 */
extern Value dcd_descr(Value setname, Value lstring, Value keyword) 
{
   Value result;
   char nolev[]="";
   char *set=absname(NULL,(char*)setname.values);
   char *key=(char*)keyword.values;
   char *lev=(char*)lstring.values;
   char key_i[10];
   fint level=0;
   fint error=0;
   int  i;
   dcdset_struct *levels;

   if (!initialized) initialize();
   if (!lev) lev=nolev;  /* ... should this be taken care of in caller ? */
                                              /* check set existence and lock */
   (void)absname(NULL,set);
   gds_lock_c(tofchar(set), &error);
   if (error<0) {
      result.type = NONE;
      FreeVar(&setname);
      FreeVar(&lstring);
      FreeVar(&keyword);
      dcderror(error-1000);
      return result;
   }
   strcpy(key_i,key);
   for(i=0; key_i[i]; i++) key_i[i] = toupper(key_i[i]); /* uppercase keyword */

                                              /* no level specified: one item */
   if (lstring.type==NONE) {
      gdsd_rdble_c(tofchar(set),tofchar(key_i),&level,&result.value,&error);
      if (!error || error==-45 || error==-46 || error==-47) {
         result.type = DOUBLE;
      } else {
         result.type = NONE;
         dcderror(error-1000);
      }
                                                             /* obtain levels */
   } else {
      char *string=(char*)malloc(strlen(set)+strlen(lev)+2);
      strcpy(string,set); strcat(string," "); strcat(string,lev);
      levels = dcdset(string);
      Delete(string);
      if (levels->status > 0) {
         result = NewList(levels->status);
         if (result.type==LIST) for (i=0; i<levels->status; i++) {
            error = 0;
            gdsd_rdble_c(tofchar(set),tofchar(key_i),&levels->subsets[i],
                         &result.values[i],&error);
            if (error && error!=-45 && error!=-46 && error!=-47) {
               FreeVar(&result);
               dcderror(error-1000);
               break;
            }
         }
         if (result.num==1 && result.type==LIST) {
            result.value = result.values[0];
            FreeVar(&result);
            result.type  = DOUBLE;
         }
      } else {
         dcderror(levels->status-2000);
         result.type = NONE;
      }
   }
   error = 0;
   gds_unlock_c(tofchar(set),&error);
   FreeVar(&setname);
   FreeVar(&lstring);
   FreeVar(&keyword);
   return result;
}

/* ========================================================================== */
/*                                 dcd_image                                  */
/* -------------------------------------------------------------------------- */
/*  dcd_image() reads data from a set's image.
 *
 *  Arguments:
 *               setname  -  TEXT structure with the set name;
 *               lstring  -  TEXT structure with a subset specification OR
 *                           NONE-type structure if subset not specified;
 *               boxval   -  TEXT structure with the image box specification.
 *
 *  Returns:     DOUBLE or LIST structure with the image values.
 *               NONE-type structure if error occurred.
 */
extern Value dcd_image(Value setname, Value lstring, Value boxval)
{
   Value result;
   char nolev[]="";
   char *set=absname(NULL,(char*)setname.values);
   char *lev=(char*)lstring.values;
   char *box=(char*)boxval.values;
   char *string;
   fint error=0;
   int  i,j;
   dcdset_struct *levels;
    
   if (!initialized) initialize();
   if (!lev) lev=nolev;     /* ... should this be taken care of in caller ? */
                                              /* check set existence and lock */
   gds_lock_c(tofchar(set), &error);
   if (error<0) {
      result.type = NONE;
      FreeVar(&setname);
      FreeVar(&lstring);
      FreeVar(&boxval);
      dcderror(error-1000);
      return result;
   }
   string = (char*)malloc(strlen(set)+strlen(lev)+2);
   strcpy(string,set); strcat(string," "); 
   if (lstring.type != NONE) strcat(string,lev);
   levels = dcdset(string);
   Delete(string);
   if (levels->status > 0) {
      fint boxerr;
      fint dcdopt=0;
      fint subset=levels->subsets[0];
      double pos[2*GDS_MAXDIM];
      fint  ipos[2*GDS_MAXDIM];
      fint ndims=gdsc_ndims_c(tofchar(set),&subset);
      fint lopos=0, hipos=0;
      fint npix;
      fint trid;
      fint ndone;
      float *pixels;
      boxerr = dcdpos_c( tofchar(set), &subset, tofchar(box), pos, &dcdopt );
      switch (boxerr) {
         case 1: {
            boxerr = 0;
            for (i=0; i<ndims; i++) {
               fint p = NINT(pos[i]);
               ipos[i] = p;
               ipos[i+ndims] = p;
            }
            break;
         }
         case 3: {
            boxerr = 0;
            for (i=0; !boxerr && i<ndims; i++) {
            fint c = NINT(pos[i]);
            fint s = NINT(pos[i+ndims]);
	        
               if (s<=0) {
                  dcderror(BADBOX);
                  boxerr = -1;
               } else {
                  ipos[i+ndims] = ( c + s ) / 2;
                  ipos[i] = ipos[i+ndims] + 1 - s;
               }
            }
            break;
         }
         case 4: {
            boxerr = 0;
            for (i=0; i<ndims; i++) {
               fint l = NINT(pos[i]);
               fint u = NINT(pos[i+ndims]);

               if (l>u) {
                 ipos[i] = u;
                  ipos[i+ndims] = l;
               } else {
                  ipos[i] = l;
                  ipos[i+ndims] = u;
               }
            }
            break;
         }
         default: dcderror(BADBOX);
      }
      if (!boxerr) {
         lopos = gdsc_fill_c( tofchar(set), &subset, ipos);
         hipos = gdsc_fill_c( tofchar(set), &subset, ipos+ndims);
         if (lopos<0 || hipos<0) {
            dcderror(BADBOX);
            boxerr = -1;
         }
      }
      if (!boxerr) {
         for (npix = 1, i=0; i<ndims; i++) {
            npix = npix*(ipos[i+ndims]-ipos[i]+1);
         }
         result = NewList(npix*levels->status);
         if (result.type==LIST) {
            pixels = NNew(npix,float);
            j=0;
            while(1) {
               trid = 0;
               gdsi_read_c(tofchar(set), &lopos, &hipos, 
                        pixels, &npix, &ndone, &trid);
               for (i=0; i<npix; i++) {
                  if (pixels[i] == blank) {
                     result.values[j*npix+i] = blankvalue;
                  } else {
                     result.values[j*npix+i] = pixels[i];
                  }
               }
               j++;
               if (j<levels->status) {
                  subset = levels->subsets[j];
                  lopos = gdsc_fill_c( tofchar(set), &subset, ipos);
                  hipos = gdsc_fill_c( tofchar(set), &subset, ipos+ndims);
               } else break;
            }
            Delete(pixels);
            if (trid != 0) dcderror(trid-1000);
            if (result.num==1 && result.type==LIST) {
               result.value = result.values[0];
               FreeVar(&result);
               result.type  = DOUBLE;
            }
         }
      }
   } else {
      dcderror(BADSUB);
      result.type = NONE;
   }
   error = 0;
   gds_unlock_c(tofchar(set), &error);
   FreeVar(&setname);
   FreeVar(&lstring);
   FreeVar(&boxval);
   return result;
}

/* ========================================================================== */
/*                                 dcd_table                                  */
/* -------------------------------------------------------------------------- */
/*  dcd_table() reads data from a set's table.
 *
 *  Arguments:
 *               setname  -  TEXT structure with the set name;
 *               lstring  -  TEXT structure with a subset specification OR
 *                           NONE-type structure if subset not specified;
 *               tabval   -  TEXT structure with the table name;
 *               colval   -  TEXT structure with the column name;
 *               rows     -  TEXT structure with the range.
 *
 *  Returns:     DOUBLE or LIST structure with the table values.
 *               NONE-type structure if error occurred.
 */
extern Value dcd_table(Value setname, Value lstring,
                       Value tabval, Value colval, Value rows)
{
   Value result={NONE, 0, 0.0, NULL};
   int   rtype, start, end, ncells;
   char nolev[]="";
   char *set=absname(NULL,(char*)setname.values);
   char *lev=(char*)lstring.values;
   char *tab=(char*)tabval.values;
   char *col=(char*)colval.values;
   char *string;
   fint error=0;
   int  i;
   dcdset_struct *levels;
    
   if (!initialized) initialize();
   if (!lev) lev=nolev;      /* ... should this be taken care of in caller ? */
                                              /* check set existence and lock */
   gds_lock_c(tofchar(set), &error);
   if (error<0) {
      result.type = NONE;
      FreeVar(&setname);
      FreeVar(&lstring);
      FreeVar(&tabval);
      FreeVar(&colval);
      FreeVar(&rows);
      dcderror(error-1000);
      return result;
   }
   string = (char*)malloc(strlen(set)+strlen(lev)+2);
   strcpy(string,set); strcat(string," "); 
   if (lstring.type != NONE) strcat(string,lev);
   levels = dcdset(string);
   Delete(string);
   if (levels->status == 1) {
      fint level = levels->subsets[0];
      fint nrows = 0;
      char coltype;
      char ctypec[9], ccomc[81], cuntsc[9];
      fchar ctype, ccom, cunts;
      ctype.a = ctypec; ccom.a = ccomc; cunts.a = cuntsc;
      ctype.l = 8;      ccom.l = 80;    cunts.l = 8; 
      ctypec[8]='\0';   ccomc[80]='\0'; cuntsc[8]='\0';
      gdsa_colinq_c(tofchar(set), &level, tofchar(tab), tofchar(col),
                    ctype, ccom, cunts, &nrows, &error);
      if (error>=0) {
         start = 1; end = nrows;
         rtype = dcdrange((char*)rows.values,&start,&end);
         if (rtype<0 || start>end || end>nrows || start<1) {
            dcderror(BADRANG);
            error = BADRANG;
         } else {
            ncells = end-start+1;
            result = NewList(end-start+1);
         }
         if (result.type != LIST) error = -1;
      }
      if (error>=0) {
         coltype = *ctypec;
         switch (coltype) {
            case 'I': {
               fint *cells=NNew(ncells,fint);
               gdsa_rcint_c(tofchar(set), &level, tofchar(tab), tofchar(col),
                            cells, (fint*)&start, (fint*)&ncells, &error);
               for (i=0; i<ncells; i++) result.values[i] = cells[i];
               Delete(cells);
               break;
            }
            case 'R': {
               float *cells=NNew(result.num,float);
               gdsa_rcreal_c(tofchar(set), &level, tofchar(tab),tofchar(col),
                             cells, (fint*)&start, (fint*)&ncells, &error);
               for (i=0; i<ncells; i++) {
                  if (cells[i] == blank) {
                     result.values[i] = blankvalue;
                  } else {
                     result.values[i] = cells[i];
                  }
               }
               Delete(cells);
               break;
            }
            case 'D': {
               gdsa_rcdble_c(tofchar(set), &level, tofchar(tab),tofchar(col),
                         result.values, (fint*)&start, (fint*)&ncells, &error);
               for (i=0; i<ncells; i++) {
                  if (result.values[i] == dblank) result.values[i] = blankvalue;
               }
               break;
            }
            default: {
               dcderror(BADCTYP);
               result.type = NONE;
            }
         }
         if (error<0) {
            FreeVar(&result);
            dcderror(error-1000);
         }

      } else {
         dcderror(error-1000);
         FreeVar(&result);
      }      
   } else {
      dcderror(BADISUB);
      result.type = NONE;
   }
   if (result.num==1 && result.type==LIST) {
      result.value = result.values[0];
      FreeVar(&result);
      result.type  = DOUBLE;
   }
   error = 0;
   gds_unlock_c(tofchar(set), &error);
   FreeVar(&setname);
   FreeVar(&lstring);
   FreeVar(&tabval);
   FreeVar(&colval);
   FreeVar(&rows);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 initialize                                 */
/* -------------------------------------------------------------------------- */
/*  initialize() initializes the module.
 *  It sets GDS immediate mode because Hermes cannot afford to wait for locked
 *  sets.
 */
static void initialize(void)
{
   bool true=TRUE;
   gdst_immediate_c(&true);
   setfblank_c(&blank);
   setdblank_c(&dblank);
   blankvalue = GetVar("BLANK").value;
   initialized = 1;
}
#<

#>            dcdmain.c
#include "dcdparse.h"
main()
{
   char text[81];
   double numbers[100];
   int  i, n, error;
   
   while (1) {
      printf("Input: ");
      for (i=0; i<80; i++) {
         char c;
         scanf("%c",&c);
         if (c=='\n') break;
         text[i] = c;
      }
      text[i] = '\0';
      n = DecodeDouble(text,numbers,100,&error);
      if (error<0) {
         printf("Error: %s\n",dcderrstr(error));
      } else {
         for (i=0; i<n; i++) {
            if (dblank_c(&numbers[i])) printf("BLANK\n");
            else printf("       %lg\n",numbers[i]);
         }
      }
#if 0 /* ... check for memory leaks */
      memorymap();
#endif
   }
}
#<

#>            dcdparse.c

# line 2 "dcdparse.y"
/* dcdparse.y


                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

Parser for numeric user input.
Author: J.P. Terlouw

                                 NOTICE

The current makefiles call yacc only on hp9000s700 systems. Other
architectures compile dcdparse.c without calling yacc.
*/

#include "gipsyc.h"
#include "stdlib.h"
#include "stdio.h"
#include "ctype.h"
#include "math.h"
#include "float.h"
#include "setfblank.h"
#include "setdblank.h"
#include "fblank.h"
#include "dblank.h"
#include "callback.h"
#include "dcdcom.h"
#include "varmgr.h"
#include "dcdgds.h"
#include "gds_closeall.h"
#include "dcderrstr.h"
#include "dcdfile.h"
#include "dcdscan.h"
#include "dcdparse.h"

#define  ERRCHK if (errcode) {  YYERROR; }
#define  SETERR(n) if (!errcode) errcode = n
#define  ELEM(var,index) (var.type==DOUBLE?var.value:var.values[i])
#define  yyparse dcd_parse             /* define own name */
#define  yyerror dcd_error

typedef struct {
   char   *fname;       /* function name */
   Value  (*fun)();     /* pointer to function */
} valfun;

typedef struct {
   char   *fname;       /* function name */
   int    nargs;        /* number of arguments */
   double (*fun)();     /* pointer to function */
} arfun;

typedef double (*Monop)(double);
typedef double (*Binop)(double, double);

static int do_parse(char *expr);

static Value fun(Value a, Arglst b);
static Value lst(Value a, Value b);
static Value lsti(Value a, Value b, Value c);
static Value lstn(Value a, Value b);
static Value lstsel(Value a, Value b);
static Value var(Value a);  
static Value num(Value a);
static void  asn(Value, Value b);
static Value monop(Monop op, Value a);
static Value binop(Binop op, Value a, Value b);
static Value tetrop(Binop op, Value a, Value b, Value c, Value d);
static double add_i(double arg1, double arg2);
static double sub_i(double a, double b);
static double mul_i(double a, double b);
static double div_i(double a, double b);
static double pwr(double a, double b);
static double neg_i(double a);
static double abs_i(double a);
static double sqrt_i(double a);
static double sin_i(double a);
static double sind_i(double a);
static double asin_i(double a);
static double asind_i(double a);
static double cos_i(double a);
static double cosd_i(double a);
static double acos_i(double a);
static double acosd_i(double a);
static double tan_i(double a);
static double tand_i(double a);
static double atan_i(double a);
static double atand_i(double a);
static double exp_i(double a);
static double sinh_i(double a);
static double ln_i(double a);
static double cosh_i(double a);
static double log_i(double a);
static double tanh_i(double a);
static double rad_i(double a);
static double deg_i(double a);
static double erf_i(double a);
static double erfc_i(double a);
static double max_i(double a, double b);
static double min_i(double a, double b);
static double sinc_i(double a);
static double atan2_i(double a, double b);
static double atand2_i(double a, double b);
static double sign_i(double a);
static double mod_i(double a, double b);
static double int_i(double a);
static double nint_i(double a);
static double ranu_i(double a, double b);
static double ranp_i(double a);
static double rang_i(double a, double b);
static double ifgt(double a, double b);
static double iflt(double a, double b);
static double ifge(double a, double b);
static double ifle(double a, double b);
static double ifeq(double a, double b);
static double ifne(double a, double b);

static Value lstcount(Arglst b);
static Value lstmean(Arglst b);
static Value lstsum(Arglst b);

static int    errcode=0;
static Arglst arg;
static double blankvalue=0.0;
static Value  NullVal={NONE,0,0.0,NULL};
static Value  UnitVal={DOUBLE,0,1.0,NULL};
static Value  resval;

static CallbackChain cbchain=NULL;

/*
 *   Functions to be called from monop(), binop() or tetrop().
 *   If the number of arguments is -1, the function is called directly.
 *   In this case it knows its number of arguments itself.
 */
static arfun arfuns[]={
   {"ABS",    1, abs_i},
   {"SQRT",   1, sqrt_i},
   {"SIN",    1, sin_i},
   {"SIND",   1, sind_i},
   {"ASIN",   1, asin_i},
   {"ASIND",  1, asind_i},
   {"COS",    1, cos_i},
   {"COSD",   1, cosd_i},
   {"ACOS",   1, acos_i},
   {"ACOSD",  1, acosd_i},
   {"TAN",    1, tan_i},
   {"TAND",   1, tand_i},
   {"ATAN",   1, atan_i},
   {"ATAND",  1, atand_i},
   {"EXP",    1, exp_i},
   {"SINH",   1, sinh_i},
   {"LN",     1, ln_i},
   {"COSH",   1, cosh_i},
   {"LOG",    1, log_i},
   {"TANH",   1, tanh_i},
   {"RAD",    1, rad_i},
   {"DEG",    1, deg_i},
   {"ERF",    1, erf_i},
   {"ERFC",   1, erfc_i},
   {"MAX",    2, max_i},
   {"MIN",    2, min_i},
   {"SINC",   1, sinc_i},
   {"ATAN2",  2, atan2_i},
   {"ATAND2", 2, atand2_i},
   {"SIGN",   1, sign_i},
   {"MOD",    2, mod_i},
   {"INT",    1, int_i},
   {"NINT",   1, nint_i},
   {"RANU",   2, ranu_i},
   {"RANG",   2, rang_i},
   {"RANP",   1, ranp_i},
   {"IFGT",   4, ifgt},
   {"IFLT",   4, iflt},
   {"IFGE",   4, ifge},
   {"IFLE",   4, ifle},
   {"IFEQ",   4, ifeq},
   {"IFNE",   4, ifne}
};

static valfun valfuns[]={
   {"COUNT",  lstcount},
   {"MEAN",   lstmean},
   {"SUM",    lstsum}
};


# line 193 "dcdparse.y"
typedef union  {
       Value  valval;
       Arglst argval;
       } YYSTYPE;
#ifdef __cplusplus
#  include <stdio.h>
#  include <yacc.h>
#endif	/* __cplusplus */ 
# define NUMBER 257
# define IDENTIFIER 258
# define STRING 259
# define POWER 260
# define WHITE 261
# define LEXERR 262
# define TEXTFILE 263
# define DESCR 264
# define IMAGE 265
# define TABLE 266
# define UMINUS 267
# define REPEAT 268
# define BADNUM 269
#define yyclearin yychar = -1
#define yyerrok yyerrflag = 0
extern int yychar;
#ifndef YYMAXDEPTH
#define YYMAXDEPTH 150
#endif

/* __YYSCLASS defines the scoping/storage class for global objects
 * that are NOT renamed by the -p option.  By default these names
 * are going to be 'static' so that multi-definition errors
 * will not occur with multiple parsers.
 * If you want (unsupported) access to internal names you need
 * to define this to be null so it implies 'extern' scope.
 * This should not be used in conjunction with -p.
 */
#ifndef __YYSCLASS
# define __YYSCLASS static
#endif
YYSTYPE yylval;
__YYSCLASS YYSTYPE yyval;
typedef int yytabelem;
# define YYERRCODE 256

# line 318 "dcdparse.y"



/* ========================================================================== */
/*                                 EvalExpr                                   */
/* -------------------------------------------------------------------------- */
/*  EvalExpr() evaluates an expression. Its purpose is to allow Hermes
 *  to support assignment statements.
 */
extern int EvalExpr(char *expr)
{
   int result;
   
   if (!blankvalue) blankvalue = GetVar("BLANK").value;
   result = do_parse(expr);
   CleanVar();
   gds_closeall_c();
   return result;
}

/* ========================================================================== */
/*                                 DecodeReal                                 */
/* -------------------------------------------------------------------------- */
/*  DecodeReal() decodes the string present in the argument 'expr'.
 *  The resulting numbers are returned via the argument 'values'.
 *  Argument 'number' specifies the maximum expected number of values.
 *  The function returns the number of values found, or a negative
 *  error code.
 */
extern int DecodeReal(char *expr, float *values, int number, int *error)
{
   int result;
   
   if (!blankvalue) blankvalue = GetVar("BLANK").value;
   MaxList(number);
   *error = do_parse(expr);
   if (!*error) {
      if (resval.type==DOUBLE) {
         result = 1;
         if (resval.value==blankvalue) setfblank_c(values);
         else if (resval.value>( MAXFLOAT)) *values =  FLT_MAX;
         else if (resval.value<(-MAXFLOAT)) *values = -FLT_MAX;
         else *values = resval.value;
      } else {
         result = resval.num;
         if (result>number) {
            *error = BUFOVF;
         } else {
            int i;
            for (i=0; i<result; i++) {
               double curval=resval.values[i];
               if (curval==blankvalue) 
                  setfblank_c(&values[i]);
                  else if (curval>( MAXFLOAT)) values[i] =  FLT_MAX;
                  else if (curval<(-MAXFLOAT)) values[i] = -FLT_MAX;
               else 
                  values[i] = resval.values[i];
            }
         }
      }
   } else result = -1;
   CleanVar();
   gds_closeall_c();
   MaxList(0);
   return result;
}

/* ========================================================================== */
/*                                 DecodeFint                                 */
/* -------------------------------------------------------------------------- */
/*  DecodeFint() decodes the string present in the argument 'expr'.
 *  The resulting numbers are returned via the argument 'values'.
 *  Argument 'number' specifies the maximum expected number of values.
 *  The function returns the number of values found, or a negative
 *  error code.
 */
extern int DecodeFint(char *expr, fint *values, int number, int *error)
{
   int result;
   
   if (!blankvalue) blankvalue = GetVar("BLANK").value;
   MaxList(number);
   *error = do_parse(expr);
   if (!*error) {
      if (resval.type==DOUBLE) {
         result = 1;
         if (resval.value==blankvalue) *values = 0;
         else if (resval.value>( MAXFLOAT)) *values = MAXINT;
         else if (resval.value<(-MAXFLOAT)) *values = MININT;
         else if (resval.value<MININT || resval.value>MAXINT) *error = BADCONV;
         else *values = resval.value;
      } else {
         result = resval.num;
         if (result>number) {
            *error = BUFOVF;
         } else {
            int i;
            for (i=0; i<result; i++) {
               double curval=resval.values[i];
               if (curval==blankvalue) 
                  values[i] = 0;
               else if (curval>( MAXFLOAT)) values[i] = MAXINT;
               else if (curval<(-MAXFLOAT)) values[i] = MININT;
               else if (curval<MININT || curval>MAXINT) {
                  *error = BADCONV;
                  break;
               } else values[i] = resval.values[i];
            }
         }
      }
   } else result = -1;
   CleanVar();
   gds_closeall_c();
   MaxList(0);
   return result;
}

/* ========================================================================== */
/*                                 DecodeDouble                               */
/* -------------------------------------------------------------------------- */
/*  DecodeDouble() decodes the string present in the argument 'expr'.
 *  The resulting numbers are returned via the argument 'values'.
 *  Argument 'number' specifies the maximum expected number of values.
 *  The function returns the number of values found, or a negative
 *  error code.
 */
extern int DecodeDouble(char *expr, double *values, int number, int *error)
{
   int result;
   
   if (!blankvalue) blankvalue = GetVar("BLANK").value;
   MaxList(number);
   *error = do_parse(expr);
   if (!*error) {
      if (resval.type==DOUBLE) {
         result = 1;
         if (resval.value==blankvalue) setdblank_c(values);
         else if (resval.value>( MAXFLOAT)) *values =  DBL_MAX;
         else if (resval.value<(-MAXFLOAT)) *values = -DBL_MAX;
         else *values = resval.value;
      } else {
         result = resval.num;
         if (result>number) {
            *error = BUFOVF;
         } else {
            int i;
            for (i=0; i<result; i++) {
               double curval=resval.values[i];
               if (curval==blankvalue) 
                  setdblank_c(&values[i]);
               else if (curval>( MAXFLOAT)) values[i] =  DBL_MAX;
               else if (curval<(-MAXFLOAT)) values[i] = -DBL_MAX;
               else 
                  values[i] = resval.values[i];
            }
         }
      }
   } else result = -1;
   CleanVar();
   gds_closeall_c();
   MaxList(0);
   return result;
}
            
/* ========================================================================== */
/*                                 dcderror                                   */
/* -------------------------------------------------------------------------- */
/*  dcderror() is used by 'satellite' routines of this parser to report
 *  any errors detected.
 */
extern void dcderror(int code)
{
   if (!errcode) errcode = code;
}

/* ========================================================================== */
/*                                 InsertExprProc                             */
/* -------------------------------------------------------------------------- */
/* InsertExprProc() inserts a callback to be called whenever an expression
 * is evaluated. Only one callback can be defined at the moment.
 */
extern long InsertExprProc(ExprProc proc, void *data)
{
   if (!cbchain) cbchain = NewCbChain();
   return (long)RegisterCb(cbchain,(CallbackProc)proc,data);
}

/* ========================================================================== */
/*                                 RemoveExprProc                             */
/* -------------------------------------------------------------------------- */
extern void RemoveExprProc(long id)
{
   DeregisterCb(cbchain,(CallbackId)id);
}
 
/* -------------------------------------------------------------------------- */
/*                                 do_parse                                   */
/* -------------------------------------------------------------------------- */
/*  do_parse() takes care of parsing the expression supplied as its argument.
 *  If necessary, it calls any registered callback.
 */
static int do_parse(char *expr)
{
   int result;
   char first[80], last[80];

   dcdinit(expr);
   result = yyparse();
   if (cbchain) {
      char    valstr[81];
      char    *errmsg;
      ExprVal state;
      state.expr  = expr;
      state.value = valstr;
      if (result) {
         errmsg = dcderrstr(result);
         strncpy(valstr,errmsg,strlen(errmsg)+1);
      } else if (resval.type==DOUBLE) {
         if (resval.value==blankvalue) strcpy(valstr,"BLANK");
         else if (resval.value>( MAXFLOAT)) strcpy(valstr,"INF");
         else if (resval.value<(-MAXFLOAT)) strcpy(valstr,"-INF");
         else sprintf(valstr,"%lg",resval.value);
      } else if (resval.type==LIST) {
         if (resval.values[0]==blankvalue) strcpy(first,"BLANK");
         else if (resval.values[0]>( MAXFLOAT)) strcpy(first,"INF");
         else if (resval.values[0]<(-MAXFLOAT)) strcpy(first,"-INF");
         else sprintf(first,"%lg",resval.values[0]);
         if (resval.values[resval.num-1]==blankvalue) strcpy(last,"BLANK");
         else if (resval.values[resval.num-1]>( MAXFLOAT)) strcpy(last,"INF");
         else if (resval.values[resval.num-1]<(-MAXFLOAT)) strcpy(last,"-INF");
         else sprintf(last,"%lg",resval.values[resval.num-1]);
         sprintf(valstr,"List(%d): first= %s, last= %s",
                 resval.num, first, last);
      } else strcpy(valstr,"--- Internal error ---");
      InvokeCb(cbchain,(void*)&state);
   }
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lst                                        */
/* -------------------------------------------------------------------------- */
/*  lst() concatenates its arguments and returns the resulting list.
 */
static Value lst(Value a, Value b)
{
   Value result;
   int   i;
   
   if (a.type==DOUBLE && b.type==DOUBLE) {
      result = NewList(2);
      result.values[0] = a.value;
      result.values[1] = b.value;
   }
   else if (a.type==LIST && b.type==DOUBLE) {
      result = NewList(a.num+1);
      if (!errcode) {
         for (i=0; i<a.num; i++) result.values[i] = a.values[i];
         result.values[i] = b.value;
      }
      FreeVar(&a);
   }
   else if (a.type==DOUBLE && b.type==LIST) {
      result = NewList(b.num+1);
      if (!errcode) {
         result.values[0] = a.value;
         for (i=0; i<b.num; i++) result.values[i+1] = b.values[i];
      }
      FreeVar(&b);
   }
   else if (a.type==LIST && b.type==LIST) {
      result = NewList(a.num+b.num);
      if (!errcode) {
         for (i=0; i<a.num; i++) result.values[i] = a.values[i];
         for (i=0; i<b.num; i++) result.values[i+a.num] = b.values[i];
      }
      FreeVar(&a);
      FreeVar(&b);
   } else errcode = BADOPER;
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lsti                                       */
/* -------------------------------------------------------------------------- */
static Value lsti(Value a, Value b, Value c)
{
   Value result;
   double incr;
   int    i;
    
   if (a.type!=DOUBLE || b.type!=DOUBLE || c.type!=DOUBLE) {
      errcode = BADELEM;
      return result;
   }
   if (a.value==blankvalue || b.value==blankvalue || c.value==blankvalue) {
      errcode = BADELEM;
      return result;
   }
   if (a.value>MAXFLOAT || b.value>MAXFLOAT || c.value>MAXFLOAT) {
      errcode = BADELEM;
      return result;
   }
   if (a.value<(-MAXFLOAT) || b.value<(-MAXFLOAT) || c.value<(-MAXFLOAT)) {
      errcode = BADELEM;
      return result;
   }
   incr = fabs(c.value);
   if (a.value>b.value) incr = -incr;
   result =NewList((int)((b.value-a.value)/incr)+1);
   if (!errcode) for (i=0; i<result.num; i++) {
      result.values[i] = a.value + i*incr;
   }
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lstn                                       */
/* -------------------------------------------------------------------------- */
static Value lstn(Value a, Value b)
{
   Value result;
   int   nelems, i;
    
   if (a.type!=DOUBLE || b.type!=DOUBLE) {
   /* ... allow repetition of list? */
      errcode = BADELEM;
      return result;
   }
   if (b.value==blankvalue) {
      errcode = BADINCR;
      return result;
   }
   nelems = (int)b.value;
   if (nelems<1) {
      result.type = NONE;
      errcode = BADINCR;
      return result;
   }
   if (nelems==1) return a;
   result = NewList(nelems);
   if (!errcode) for (i=0; i<nelems; i++) result.values[i] = a.value;
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lstsel                                     */
/* -------------------------------------------------------------------------- */
static Value lstsel(Value a, Value b)
{
   Value result;
   int    i, index;
    
   if (a.type==DOUBLE) {
      errcode = BADOPER;
      return result;
   }
   if (b.type==DOUBLE) {
      index = (int)nint_i(b.value)-1;
      if (index<0 || index>=a.num) {
         errcode = BADINDX;
      } else {
         result.type = DOUBLE;
         result.value = a.values[index];
      }
   } else if (b.type==LIST) {
      result = b;
      for (i=0; i<b.num; i++) {
         index = (int)nint_i(b.values[i])-1;
         if (index<0 || index>=a.num) {
            errcode = BADINDX;
            break;
         }
         b.values[i] = a.values[index];
      }
   } else errcode = BADOPER;
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lstcount                                   */
/* -------------------------------------------------------------------------- */
/*  lstcount() returns the number of elements in a LIST or DOUBLE.
 */
static Value lstcount(Arglst a)
{
   Value result=NullVal;    

   if (a.num != 1) {
      errcode = BADNARG;
   } else if (a.val[0].type==DOUBLE) {
      result = UnitVal;
   } else if (a.val[0].type==LIST) {
      result.type = DOUBLE;
      result.value = a.val[0].num;
   } else errcode = BADARG;
   FreeVar(&a.val[0]);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lstmean                                    */
/* -------------------------------------------------------------------------- */
/*  lstmean() returns the average of the elements in a LIST or DOUBLE.
 */
static Value lstmean(Arglst a)
{
   Value   result;
   int     n;

   if (a.val[0].type==DOUBLE) n = 1; else n = a.val[0].num;
   result = lstsum(a);
   if (result.type==DOUBLE) result.value = div_i(result.value, (double)n);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lstsum                                     */
/* -------------------------------------------------------------------------- */
/*  lstsum() returns the sum of the elements in a LIST or DOUBLE.
 */
static Value lstsum(Arglst a)
{
   Value result=NullVal;
   int   i;

   if (a.num != 1) {
      errcode = BADNARG;
   } else if (a.val[0].type==DOUBLE) {
      result = a.val[0];
   } else if (a.val[0].type==LIST) {
      result.type = DOUBLE;
      for (i=0; i<a.val[0].num; i++) {
         result.value = add_i(result.value, a.val[0].values[i]);
      }
   } else errcode = BADARG;
   FreeVar(&a.val[0]);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 monop                                      */
/* -------------------------------------------------------------------------- */
static Value monop(Monop op, Value a)
{
   if (a.type==DOUBLE) {
      a.value = (*op)(a.value);
   } else if (a.type==LIST) {
      int i;
      for (i=0; i<a.num; i++) a.values[i] = (*op)(a.values[i]);
   } else errcode = BADARG;
   if (errcode) FreeVar(&a);
   return a;
}

/* -------------------------------------------------------------------------- */
/*                                 binop                                      */
/* -------------------------------------------------------------------------- */
static Value binop(Binop op, Value a, Value b)
{
   Value result;
    
   if (a.type==DOUBLE && b.type==DOUBLE) {
      result.type = DOUBLE;
      result.value = (*op)(a.value, b.value);
   } else if (a.type==DOUBLE && b.type==LIST) {
      int i;
      result = b;
      for (i=0; i<result.num; i++) {
         result.values[i] = (*op)(a.value, b.values[i]);
      }
      return result;
   } else if (a.type==LIST && b.type==DOUBLE) {
      int i;
      result = a;
      for (i=0; i<result.num; i++) {
         result.values[i] = (*op)(a.values[i], b.value);
      }
   } else if (a.type==LIST && b.type==LIST) {
      int i;
      if (a.num != b.num) {
         errcode = BADLSTL;
         return result;
      }
      result = a;
      for (i=0; i<result.num; i++) {
         result.values[i] = (*op)(a.values[i], b.values[i]);
      }
      FreeVar(&b);
   } else errcode = BADOPER;
   if (errcode) FreeVar(&result);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 tetrop                                     */
/* -------------------------------------------------------------------------- */
/*  tetrop() handles a four-argument function. The only four-argument 
 *  functions at present are the if-functions. The code of this function
 *  is only capable of handling these functions.
 */
static Value tetrop(Binop op, Value a, Value b, Value c, Value d)
{
   Value result;
   Value compare;
   int   nelems, i;
    
   compare = binop(op, a, b);
   if (errcode) return result;
   if (compare.type==DOUBLE) {
      if (compare.value==blankvalue) return compare;
      else return compare.value?c:d;
          /* ... Type and number are not checked here. Do we want this? */ 
   }
   nelems = compare.num;
   if (c.type==LIST && c.num!=nelems || d.type==LIST && d.num!=nelems) {
      errcode = BADLSTL;
      FreeVar(&compare);
      return result;
   }
   result = NewList(nelems);
   for (i=0; i<nelems; i++) {
      if (compare.values[i]==blankvalue) result.values[i] = blankvalue;
      else result.values[i] = compare.values[i]?ELEM(c,i):ELEM(d,i);
   }
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 fun                                        */
/* -------------------------------------------------------------------------- */
/*  fun() looks up the function of which the name is specified in argument 'a'.
 *  If found, it checks the number of arguments and calls one of the
 *  functions monop, binop er tetrop.
 */
static Value fun(Value a, Arglst b)
{
   Value result;
   int i, nfuns=sizeof(arfuns)/sizeof(arfun);
   char *fname=(char*)a.values;
   
                                                           /* Value function? */
   for (i=0; fname[i]; i++) fname[i] = toupper(fname[i]);
   nfuns = sizeof(valfuns)/sizeof(valfun);
   for (i=0; i<nfuns; i++) {
      if (!strcmp(valfuns[i].fname,fname)) {
         result = (*valfuns[i].fun)(b);
         break;
      }
   }
                                                      /* arithmetic function? */
   if (i==nfuns) nfuns = sizeof(arfuns)/sizeof(arfun); else nfuns = -1;
   for (i=0; i<nfuns; i++) {
      if (!strcmp(arfuns[i].fname,fname)) {
         if (b.num != arfuns[i].nargs) {
            errcode = BADNARG;
         } else {
            switch (b.num) {
               case  1: result = monop((Monop)arfuns[i].fun, b.val[0]);
                        break;
               case  2: result = binop((Binop)arfuns[i].fun,
                                        b.val[0], b.val[1]); break;
               case  4: result = tetrop((Binop)arfuns[i].fun,
                                         b.val[0], b.val[1],
                                         b.val[2], b.val[3]); break;
               default: errcode = BADNARG; break;
            }
         }
         break;
      }
   }
   if (i==nfuns) errcode = BADFUN;
   FreeVar(&a);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 var                                        */
/* -------------------------------------------------------------------------- */
/*  var() returns the value of the variable of which the name is specified
 *  in argument 'a'.
 */
static Value var(Value a)
{
   Value result;
    
   result = GetVar((char*)a.values);
   FreeVar(&a);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 asn                                        */
/* -------------------------------------------------------------------------- */
/*  asn() assigns the value in argument 'b' to the variable of which the name
 *  is specified in argument 'a'.
 */
static void  asn(Value a, Value b)
{
   errcode = PutVar((char*)a.values, b);
   FreeVar(&a);
}

yyerror (char *s)
{
   dcdmode(0);
   errcode = BADPSYN;
}

/* -------------------------------------------------------------------------- */
/*                   A R I T H M E T I C   F U N C T I O N S                  */
/* -------------------------------------------------------------------------- */
static double neg_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return -arg1;
   }
}

static double add_i(double arg1, double arg2)
{
   double result;

   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      result = blankvalue;
   } else {
      result = arg1+arg2;
      if (fabs(result)>MAXFLOAT) {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
   return result;
}

static double sub_i(double arg1, double arg2)
{
   double result;
   
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      result = blankvalue; 
   } else {
      result = arg1-arg2;
      if (fabs(result)>MAXFLOAT) {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
   return result;
}

static double mul_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else if ((arg1 == 0.0) || (arg2 == 0.0)) {
      return 0.0;
   } else {
      double intsum = (log10(fabs(arg1))+log10(fabs(arg2)));
      if ((MINLOG < intsum) && (intsum < MAXLOG)) {
         return arg1*arg2;
      } else {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
}
      
static double div_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else if (arg2 == 0.0) {
      SETERR(ERARITH);
      return blankvalue;
   } else if (arg1 == 0.0) {
      return 0.0;
   } else {
      double intsum = (log10(fabs(arg2))-log10(fabs(arg1)));
      if ((MINLOG < intsum)&&(intsum < MAXLOG)) {
         return arg1/arg2;
      } else {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
}

static double pwr(double arg1, double arg2)
{
   double intsum;
   
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) return blankvalue;
   if (arg1==0.0) {
      if (arg2>0) return 0.0;
      else {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
   intsum = log10(fabs(arg1))*arg2;
   if (intsum>MAXLOG || intsum<MINLOG) {
      SETERR(ERARITH);
      return blankvalue;
   }
   if (arg1 >= 0.0) {
      return pow(arg1,arg2);
   } else {
      int p = arg2, t;
      double epsilon = 0.000001;
      if (fabs(arg2 - p) <= epsilon) {
         t = (p % 2 == 0) ? 1 : -1;
         return t*pow(fabs(arg1),arg2);
      } else {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
}

static double sin_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return sin(arg1);
   }
}

static double sind_i(double arg1)
{
   return sin_i(rad_i(arg1));
}

static double asin_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) > 1) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return asin(arg1);
   }
}

static double asind_i(double arg1)
{
   return deg_i(asin_i(arg1));
}

static double sinh_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) > 70) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return sinh(arg1);
   }
}

static double cos_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return cos(arg1);
   }
}

static double cosd_i(double arg1)
{
   return cos_i(rad_i(arg1));
}

static double acos_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) > 1) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return acos(arg1);
   }
}

static double acosd_i(double arg1)
{
   return deg_i(acos_i(arg1));
}

static double cosh_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) > 70) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return cosh(arg1);
   }
}

static double tan_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return tan(arg1);
   }
}

static double tand_i(double arg1)
{
   return tan_i(rad_i(arg1));
}

static double atan_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return atan(arg1);
   }
}

static double atand_i(double arg1)
{
   return deg_i(atan_i(arg1));
}

static double tanh_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) > 70) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return tanh(arg1);
   }
}

static double atan2_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else {
      return atan2(arg1,arg2);
   }
}

static double atand2_i(double arg1, double arg2)
{
   return deg_i(atan2_i(arg1, arg2));
}

static double rad_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return arg1*0.017453292519943295769237;
   }
}

static double deg_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return arg1*57.295779513082320876798155;
   }
}

static double exp_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 > 70) {
      SETERR(ERARITH);
      return blankvalue;
   } else if (arg1 < -70) {
      return 0.0;
   } else {
      return exp(arg1);
   }
}

static double ln_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 > 0) {
      return log(arg1);
   } else {
      SETERR(ERARITH);
      return blankvalue;
   }
}

static double log_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 > 0) {
      return log10(arg1);
   } else {
      SETERR(ERARITH);
      return blankvalue;
   }
}

static double sqrt_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 < 0) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return sqrt(arg1);
   }
}

static double abs_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return fabs(arg1);
   }
}

static double sinc_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) < 1.0e-30) {
      return 1.0;
   } else {
      return sin(arg1)/arg1;
   }
}

static double max_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else if (arg1 > arg2) {
      return arg1;
   } else {
      return arg2;
   }
}

static double min_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else if (arg1 < arg2) {
      return arg1;
   } else {
      return arg2;
   }
}

static double erf_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      double p  =  0.327591100;
      double a1 =  0.254829592;
      double a2 = -0.284496736;
      double a3 =  1.421413741;
      double a4 = -1.453152027;
      double a5 =  1.061405429;
      double t1 = 1.0 / ( 1.0 + p * fabs(arg1));
      double t2 = t1*t1, t3 = t1*t2, t4 = t1*t3, t5 = t4*t1;
      if (arg1 > 0.0) {
         return 1.0-(a1*t1+a2*t2+a3*t3+a4*t4+a5*t5)*exp(-arg1*arg1);
      } else {
         return (a1*t1+a2*t2+a3*t3+a4*t4+a5*t5)*exp(-arg1*arg1)-1.0;
      }
   }
}

static double erfc_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return 1.0-erf_i(arg1);
   }
}

static double mod_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else if (arg2 == 0.0) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
     int   xxx = arg1/arg2;
     return arg1-xxx*arg2;
   }
}

static double int_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
     int xxx = arg1;  /* this could be dangerous */
     return (double)xxx;
   }
}

static double nint_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
     int xxx = (fabs(arg1) + 0.5);  /* this could be dangerous */
     if (arg1<0) xxx = -xxx;
     return (double)xxx;
   }
}

static double sign_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 == 0.0) {
      return 0.0;
   } else if (arg1 > 0.0) {
      return  1.0;
   } else {
      return -1.0;
   }
}

static double ran_i()
{
   double xxx = rand();
   return (xxx+1.0) / ((double) RAND_MAX + 1.0);
}

static double ranu_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else {
      return arg1+ran_i()*(arg2-arg1);
   }
}

static double rang_i(double arg1, double arg2)
{
   static int oddran=0;

   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else {
     double val, r1, r2;
     r1 = ran_i();
     r2 = ran_i();
     if (oddran == 0) {
        val = sqrt(-2*log(r1))*cos(6.283185307179586476925286*r2); oddran = 1;
     } else {
        val = sqrt(-2*log(r1))*cos(6.283185307179586476925286*r2); oddran = 0;
     }
     val = arg1 + fabs(arg2) * val;
     return val;
   }
}

static double ranp_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 < 0) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      double val, cum, p, f;
      if (arg1 < 40) {
         int xxx = rang_i(arg1,sqrt(arg1))+0.5;
         val = xxx;
      } else {
         cum = exp(-arg1);
         p = cum;
         val = 0.0;
         f = ran_i();
         while ( f >= cum) {
            val = val + 1.0;
            p = p * arg1 / val;
            cum = cum + p;
         }
      }
     return val;
   }
}

/* -------------------------------------------------------------------------- */
/*                   L O G I C A L         F U N C T I O N S                  */
/* -------------------------------------------------------------------------- */
static double ifgt(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a>b?1.0:0.0;
}

static double iflt(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a<b?1.0:0.0;
}

static double ifge(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a>=b?1.0:0.0;
}

static double ifle(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a<=b?1.0:0.0;
}

static double ifeq(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a==b?1.0:0.0;
}

static double ifne(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a!=b?1.0:0.0;
}

__YYSCLASS yytabelem yyexca[] ={
-1, 1,
	0, -1,
	-2, 0,
	};
# define YYNPROD 38
# define YYLAST 316
__YYSCLASS yytabelem yyact[]={

     6,    26,    63,    12,    85,    11,    67,    28,    30,    41,
    73,    71,    62,     6,   103,    99,    12,    64,    11,    98,
    65,    89,    24,    22,    83,    23,    69,    25,    24,    29,
   106,    18,    88,    25,    24,    22,   104,    23,    27,    25,
    76,    87,    84,    19,    24,    22,   100,    23,    66,    25,
    27,    97,    55,    24,    22,    19,    23,    96,    25,    90,
    27,    86,    30,    24,    22,    19,    23,    56,    25,    27,
    31,    38,    24,    22,    19,    23,    37,    25,    36,    27,
    77,    35,    24,    22,    19,    23,    57,    25,    27,    24,
    22,    53,    23,    19,    25,    17,    24,    22,    27,    23,
    14,    25,     1,    19,    58,    59,    60,     0,     0,     0,
    19,    79,     0,    81,    82,     0,     0,    19,     0,     0,
     0,     0,     0,     0,    91,     0,     0,     0,     0,     0,
    95,     0,     0,     0,     0,     0,     0,    78,     0,    80,
   102,     0,     0,     0,   105,     0,     0,     2,     0,     0,
     0,     0,    92,     0,    32,    93,    94,     0,     0,    39,
    40,     0,     0,     0,     0,     0,   101,    42,    43,    44,
    45,    46,    47,    48,    49,    50,    51,    52,    54,     0,
     0,     0,     0,     0,     0,     0,     0,     0,     0,    61,
     0,     0,     0,     0,     0,     0,     0,     0,     0,     0,
     0,     0,     0,     0,     0,     0,     0,     0,     0,     0,
     0,     0,     0,    74,    75,     0,     5,    13,     3,     4,
     0,     0,    15,     7,     8,    10,     9,    72,    70,    16,
    13,    34,    33,     0,     0,    15,     7,     8,    10,     9,
    26,    20,    16,    68,     0,     0,    26,     0,    21,     0,
     0,     0,    26,    20,     0,     0,     0,     0,     0,     0,
    21,     0,    26,    20,     0,     0,     0,     0,     0,     0,
    21,    26,    20,     0,     0,     0,     0,     0,     0,    21,
     0,    26,    20,     0,     0,     0,     0,     0,     0,    21,
    26,    20,     0,     0,     0,     0,     0,     0,    21,     0,
    26,     0,     0,     0,     0,     0,     0,    26,    21,     0,
     0,     0,     0,     0,    26,    21 };
__YYSCLASS yytabelem yypact[]={

   -40, -3000,    21,   -54,   -32,    60,   -27,    41,    38,    36,
    31,   -27,   -27, -3000, -3000, -3000, -3000,   -49, -3000,   -27,
   -27,   -27,   -27,   -27,   -27,   -27,   -27,   -27,   -27,   -27,
   -27, -3000,    11,    22, -3000, -3000, -3000, -3000, -3000, -3000,
 -3000,   -27,   -14,    40,    54,   -14,   -14,  -259,  -259,  -259,
    47,     2,    -8,   -24,    30, -3000,     4,  -253,   -18,   -33,
   -34,    47, -3000, -3000, -3000,   -27,   -27, -3000, -3000, -3000,
 -3000, -3000, -3000, -3000,    30,   -20,    -2,  -255,    20,    -3,
   -12,   -23,    18, -3000, -3000, -3000, -3000, -3000, -3000, -3000,
 -3000,    16,    10,   -25,   -29,     5, -3000, -3000, -3000, -3000,
 -3000,   -30,    -5, -3000, -3000,   -11, -3000 };
__YYSCLASS yytabelem yypgo[]={

     0,   102,   147,    67,    40,   100,    95,    91,    86,    80 };
__YYSCLASS yytabelem yyr1[]={

     0,     1,     1,     1,     1,     2,     2,     2,     2,     2,
     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
     2,     2,     2,     2,     2,     2,     2,     2,     2,     5,
     5,     6,     8,     3,     9,     4,     7,     7 };
__YYSCLASS yytabelem yyr2[]={

     0,     5,     9,     9,     5,     7,     7,     7,     7,     7,
     7,     7,     7,     7,     9,    17,    17,    13,    25,    21,
    17,    13,     5,     5,     3,     3,     3,     3,     3,     7,
     3,     7,     1,     5,     1,     5,     3,     7 };
__YYSCLASS yytabelem yychk[]={

 -3000,    -1,    -2,   258,   259,   256,    40,   263,   264,   266,
   265,    45,    43,   257,    -5,   262,   269,    -6,    10,    63,
   261,   268,    43,    45,    42,    47,   260,    58,    61,    61,
    40,    10,    -2,   259,   258,    40,    40,    40,    40,    -2,
    -2,    58,    -2,    -2,    -2,    -2,    -2,    -2,    -2,    -2,
    -2,    -2,    -2,    -7,    -2,    41,    -3,    -8,    -3,    -3,
    -3,    -2,    10,    10,    41,    44,    44,   259,   261,    44,
   261,    44,   261,    44,    -2,    -2,    -4,    -9,    -3,    -4,
    -3,    -4,    -4,    44,    44,   259,    41,    44,    44,    44,
    41,    -4,    -3,    -3,    -3,    -4,    41,    41,    44,    44,
    41,    -3,    -4,    44,    41,    -4,    41 };
__YYSCLASS yytabelem yydef[]={

     0,    -2,     0,    24,     0,     0,     0,     0,     0,     0,
     0,     0,     0,    25,    26,    27,    28,    30,     1,     0,
     0,     0,     0,     0,     0,     0,     0,     0,     0,     0,
     0,     4,     0,     0,    24,    32,    32,    32,    32,    22,
    23,     0,     6,     7,     8,     9,    10,    11,    12,    13,
    31,     0,     0,     0,    36,     5,     0,     0,     0,     0,
     0,    29,     2,     3,    14,     0,     0,    33,    34,    32,
    34,    32,    34,    34,    37,     0,     0,     0,     0,     0,
     0,     0,     0,    34,    32,    35,    17,    32,    32,    34,
    21,     0,     0,     0,     0,     0,    15,    16,    32,    34,
    20,     0,     0,    34,    19,     0,    18 };
typedef struct { char *t_name; int t_val; } yytoktype;
#ifndef YYDEBUG
#	define YYDEBUG	0	/* don't allow debugging */
#endif

#if YYDEBUG

__YYSCLASS yytoktype yytoks[] =
{
	"NUMBER",	257,
	"IDENTIFIER",	258,
	"STRING",	259,
	"POWER",	260,
	"WHITE",	261,
	"LEXERR",	262,
	"TEXTFILE",	263,
	"DESCR",	264,
	"IMAGE",	265,
	"TABLE",	266,
	"UMINUS",	267,
	"REPEAT",	268,
	"BADNUM",	269,
	"=",	61,
	",",	44,
	":",	58,
	"+",	43,
	"-",	45,
	"?",	63,
	"*",	42,
	"/",	47,
	"-unknown-",	-1	/* ends search */
};

__YYSCLASS char * yyreds[] =
{
	"-no such reduction-",
	"stat : expr '\n'",
	"stat : IDENTIFIER '=' expr '\n'",
	"stat : STRING '=' expr '\n'",
	"stat : error '\n'",
	"expr : '(' expr ')'",
	"expr : expr '?' expr",
	"expr : expr WHITE expr",
	"expr : expr REPEAT expr",
	"expr : expr '+' expr",
	"expr : expr '-' expr",
	"expr : expr '*' expr",
	"expr : expr '/' expr",
	"expr : expr POWER expr",
	"expr : STRING '(' arglst ')'",
	"expr : TEXTFILE '(' liter ',' expr ',' gdslev ')'",
	"expr : DESCR '(' liter WHITE gdslev ',' liter ')'",
	"expr : DESCR '(' liter ',' liter ')'",
	"expr : TABLE '(' liter WHITE gdslev ',' liter ',' liter ',' gdslev ')'",
	"expr : TABLE '(' liter ',' liter ',' liter ',' gdslev ')'",
	"expr : IMAGE '(' liter WHITE gdslev ',' gdslev ')'",
	"expr : IMAGE '(' liter ',' gdslev ')'",
	"expr : '-' expr",
	"expr : '+' expr",
	"expr : IDENTIFIER",
	"expr : NUMBER",
	"expr : list",
	"expr : LEXERR",
	"expr : BADNUM",
	"list : lintro ':' expr",
	"list : lintro",
	"lintro : expr ':' expr",
	"liter : /* empty */",
	"liter : STRING",
	"gdslev : /* empty */",
	"gdslev : STRING",
	"arglst : expr",
	"arglst : arglst ',' expr",
};
#endif /* YYDEBUG */
#define YYFLAG  (-3000)
/* @(#) $Revision: 70.7 $ */    

/*
** Skeleton parser driver for yacc output
*/

#if defined(NLS) && !defined(NL_SETN)
#include <msgbuf.h>
#endif

#ifndef nl_msg
#define nl_msg(i,s) (s)
#endif

/*
** yacc user known macros and defines
*/
#define YYERROR		goto yyerrlab

#ifndef __RUNTIME_YYMAXDEPTH
#define YYACCEPT	return(0)
#define YYABORT		return(1)
#else
#define YYACCEPT	{free_stacks(); return(0);}
#define YYABORT		{free_stacks(); return(1);}
#endif

#define YYBACKUP( newtoken, newvalue )\
{\
	if ( yychar >= 0 || ( yyr2[ yytmp ] >> 1 ) != 1 )\
	{\
		yyerror( (nl_msg(30001,"syntax error - cannot backup")) );\
		goto yyerrlab;\
	}\
	yychar = newtoken;\
	yystate = *yyps;\
	yylval = newvalue;\
	goto yynewstate;\
}
#define YYRECOVERING()	(!!yyerrflag)
#ifndef YYDEBUG
#	define YYDEBUG	1	/* make debugging available */
#endif

/*
** user known globals
*/
int yydebug;			/* set to 1 to get debugging */

/*
** driver internal defines
*/
/* define for YYFLAG now generated by yacc program. */
/*#define YYFLAG		(FLAGVAL)*/

/*
** global variables used by the parser
*/
# ifndef __RUNTIME_YYMAXDEPTH
__YYSCLASS YYSTYPE yyv[ YYMAXDEPTH ];	/* value stack */
__YYSCLASS int yys[ YYMAXDEPTH ];		/* state stack */
# else
__YYSCLASS YYSTYPE *yyv;			/* pointer to malloc'ed value stack */
__YYSCLASS int *yys;			/* pointer to malloc'ed stack stack */

#if defined(__STDC__) || defined (__cplusplus)
#include <stdlib.h>
#else
	extern char *malloc();
	extern char *realloc();
	extern void free();
#endif /* __STDC__ or __cplusplus */


static int allocate_stacks(); 
static void free_stacks();
# ifndef YYINCREMENT
# define YYINCREMENT (YYMAXDEPTH/2) + 10
# endif
# endif	/* __RUNTIME_YYMAXDEPTH */
long  yymaxdepth = YYMAXDEPTH;

__YYSCLASS YYSTYPE *yypv;			/* top of value stack */
__YYSCLASS int *yyps;			/* top of state stack */

__YYSCLASS int yystate;			/* current state */
__YYSCLASS int yytmp;			/* extra var (lasts between blocks) */

int yynerrs;			/* number of errors */
__YYSCLASS int yyerrflag;			/* error recovery flag */
int yychar;			/* current input token number */



/*
** yyparse - return 0 if worked, 1 if syntax error not recovered from
*/
int
yyparse()
{
	register YYSTYPE *yypvt;	/* top of value stack for $vars */

	/*
	** Initialize externals - yyparse may be called more than once
	*/
# ifdef __RUNTIME_YYMAXDEPTH
	if (allocate_stacks()) YYABORT;
# endif
	yypv = &yyv[-1];
	yyps = &yys[-1];
	yystate = 0;
	yytmp = 0;
	yynerrs = 0;
	yyerrflag = 0;
	yychar = -1;

	goto yystack;
	{
		register YYSTYPE *yy_pv;	/* top of value stack */
		register int *yy_ps;		/* top of state stack */
		register int yy_state;		/* current state */
		register int  yy_n;		/* internal state number info */

		/*
		** get globals into registers.
		** branch to here only if YYBACKUP was called.
		*/
	yynewstate:
		yy_pv = yypv;
		yy_ps = yyps;
		yy_state = yystate;
		goto yy_newstate;

		/*
		** get globals into registers.
		** either we just started, or we just finished a reduction
		*/
	yystack:
		yy_pv = yypv;
		yy_ps = yyps;
		yy_state = yystate;

		/*
		** top of for (;;) loop while no reductions done
		*/
	yy_stack:
		/*
		** put a state and value onto the stacks
		*/
#if YYDEBUG
		/*
		** if debugging, look up token value in list of value vs.
		** name pairs.  0 and negative (-1) are special values.
		** Note: linear search is used since time is not a real
		** consideration while debugging.
		*/
		if ( yydebug )
		{
			register int yy_i;

			printf( "State %d, token ", yy_state );
			if ( yychar == 0 )
				printf( "end-of-file\n" );
			else if ( yychar < 0 )
				printf( "-none-\n" );
			else
			{
				for ( yy_i = 0; yytoks[yy_i].t_val >= 0;
					yy_i++ )
				{
					if ( yytoks[yy_i].t_val == yychar )
						break;
				}
				printf( "%s\n", yytoks[yy_i].t_name );
			}
		}
#endif /* YYDEBUG */
		if ( ++yy_ps >= &yys[ yymaxdepth ] )	/* room on stack? */
		{
# ifndef __RUNTIME_YYMAXDEPTH
			yyerror( (nl_msg(30002,"yacc stack overflow")) );
			YYABORT;
# else
			/* save old stack bases to recalculate pointers */
			YYSTYPE * yyv_old = yyv;
			int * yys_old = yys;
			yymaxdepth += YYINCREMENT;
			yys = (int *) realloc(yys, yymaxdepth * sizeof(int));
			yyv = (YYSTYPE *) realloc(yyv, yymaxdepth * sizeof(YYSTYPE));
			if (yys==0 || yyv==0) {
			    yyerror( (nl_msg(30002,"yacc stack overflow")) );
			    YYABORT;
			    }
			/* Reset pointers into stack */
			yy_ps = (yy_ps - yys_old) + yys;
			yyps = (yyps - yys_old) + yys;
			yy_pv = (yy_pv - yyv_old) + yyv;
			yypv = (yypv - yyv_old) + yyv;
# endif

		}
		*yy_ps = yy_state;
		*++yy_pv = yyval;

		/*
		** we have a new state - find out what to do
		*/
	yy_newstate:
		if ( ( yy_n = yypact[ yy_state ] ) <= YYFLAG )
			goto yydefault;		/* simple state */
#if YYDEBUG
		/*
		** if debugging, need to mark whether new token grabbed
		*/
		yytmp = yychar < 0;
#endif
		if ( ( yychar < 0 ) && ( ( yychar = yylex() ) < 0 ) )
			yychar = 0;		/* reached EOF */
#if YYDEBUG
		if ( yydebug && yytmp )
		{
			register int yy_i;

			printf( "Received token " );
			if ( yychar == 0 )
				printf( "end-of-file\n" );
			else if ( yychar < 0 )
				printf( "-none-\n" );
			else
			{
				for ( yy_i = 0; yytoks[yy_i].t_val >= 0;
					yy_i++ )
				{
					if ( yytoks[yy_i].t_val == yychar )
						break;
				}
				printf( "%s\n", yytoks[yy_i].t_name );
			}
		}
#endif /* YYDEBUG */
		if ( ( ( yy_n += yychar ) < 0 ) || ( yy_n >= YYLAST ) )
			goto yydefault;
		if ( yychk[ yy_n = yyact[ yy_n ] ] == yychar )	/*valid shift*/
		{
			yychar = -1;
			yyval = yylval;
			yy_state = yy_n;
			if ( yyerrflag > 0 )
				yyerrflag--;
			goto yy_stack;
		}

	yydefault:
		if ( ( yy_n = yydef[ yy_state ] ) == -2 )
		{
#if YYDEBUG
			yytmp = yychar < 0;
#endif
			if ( ( yychar < 0 ) && ( ( yychar = yylex() ) < 0 ) )
				yychar = 0;		/* reached EOF */
#if YYDEBUG
			if ( yydebug && yytmp )
			{
				register int yy_i;

				printf( "Received token " );
				if ( yychar == 0 )
					printf( "end-of-file\n" );
				else if ( yychar < 0 )
					printf( "-none-\n" );
				else
				{
					for ( yy_i = 0;
						yytoks[yy_i].t_val >= 0;
						yy_i++ )
					{
						if ( yytoks[yy_i].t_val
							== yychar )
						{
							break;
						}
					}
					printf( "%s\n", yytoks[yy_i].t_name );
				}
			}
#endif /* YYDEBUG */
			/*
			** look through exception table
			*/
			{
				register int *yyxi = yyexca;

				while ( ( *yyxi != -1 ) ||
					( yyxi[1] != yy_state ) )
				{
					yyxi += 2;
				}
				while ( ( *(yyxi += 2) >= 0 ) &&
					( *yyxi != yychar ) )
					;
				if ( ( yy_n = yyxi[1] ) < 0 )
					YYACCEPT;
			}
		}

		/*
		** check for syntax error
		*/
		if ( yy_n == 0 )	/* have an error */
		{
			/* no worry about speed here! */
			switch ( yyerrflag )
			{
			case 0:		/* new error */
				yyerror( (nl_msg(30003,"syntax error")) );
				yynerrs++;
				goto skip_init;
			yyerrlab:
				/*
				** get globals into registers.
				** we have a user generated syntax type error
				*/
				yy_pv = yypv;
				yy_ps = yyps;
				yy_state = yystate;
				yynerrs++;
			skip_init:
			case 1:
			case 2:		/* incompletely recovered error */
					/* try again... */
				yyerrflag = 3;
				/*
				** find state where "error" is a legal
				** shift action
				*/
				while ( yy_ps >= yys )
				{
					yy_n = yypact[ *yy_ps ] + YYERRCODE;
					if ( yy_n >= 0 && yy_n < YYLAST &&
						yychk[yyact[yy_n]] == YYERRCODE)					{
						/*
						** simulate shift of "error"
						*/
						yy_state = yyact[ yy_n ];
						goto yy_stack;
					}
					/*
					** current state has no shift on
					** "error", pop stack
					*/
#if YYDEBUG
#	define _POP_ "Error recovery pops state %d, uncovers state %d\n"
					if ( yydebug )
						printf( _POP_, *yy_ps,
							yy_ps[-1] );
#	undef _POP_
#endif
					yy_ps--;
					yy_pv--;
				}
				/*
				** there is no state on stack with "error" as
				** a valid shift.  give up.
				*/
				YYABORT;
			case 3:		/* no shift yet; eat a token */
#if YYDEBUG
				/*
				** if debugging, look up token in list of
				** pairs.  0 and negative shouldn't occur,
				** but since timing doesn't matter when
				** debugging, it doesn't hurt to leave the
				** tests here.
				*/
				if ( yydebug )
				{
					register int yy_i;

					printf( "Error recovery discards " );
					if ( yychar == 0 )
						printf( "token end-of-file\n" );
					else if ( yychar < 0 )
						printf( "token -none-\n" );
					else
					{
						for ( yy_i = 0;
							yytoks[yy_i].t_val >= 0;
							yy_i++ )
						{
							if ( yytoks[yy_i].t_val
								== yychar )
							{
								break;
							}
						}
						printf( "token %s\n",
							yytoks[yy_i].t_name );
					}
				}
#endif /* YYDEBUG */
				if ( yychar == 0 )	/* reached EOF. quit */
					YYABORT;
				yychar = -1;
				goto yy_newstate;
			}
		}/* end if ( yy_n == 0 ) */
		/*
		** reduction by production yy_n
		** put stack tops, etc. so things right after switch
		*/
#if YYDEBUG
		/*
		** if debugging, print the string that is the user's
		** specification of the reduction which is just about
		** to be done.
		*/
		if ( yydebug )
			printf( "Reduce by (%d) \"%s\"\n",
				yy_n, yyreds[ yy_n ] );
#endif
		yytmp = yy_n;			/* value to switch over */
		yypvt = yy_pv;			/* $vars top of value stack */
		/*
		** Look in goto table for next state
		** Sorry about using yy_state here as temporary
		** register variable, but why not, if it works...
		** If yyr2[ yy_n ] doesn't have the low order bit
		** set, then there is no action to be done for
		** this reduction.  So, no saving & unsaving of
		** registers done.  The only difference between the
		** code just after the if and the body of the if is
		** the goto yy_stack in the body.  This way the test
		** can be made before the choice of what to do is needed.
		*/
		{
			/* length of production doubled with extra bit */
			register int yy_len = yyr2[ yy_n ];

			if ( !( yy_len & 01 ) )
			{
				yy_len >>= 1;
				yyval = ( yy_pv -= yy_len )[1];	/* $$ = $1 */
				yy_state = yypgo[ yy_n = yyr1[ yy_n ] ] +
					*( yy_ps -= yy_len ) + 1;
				if ( yy_state >= YYLAST ||
					yychk[ yy_state =
					yyact[ yy_state ] ] != -yy_n )
				{
					yy_state = yyact[ yypgo[ yy_n ] ];
				}
				goto yy_stack;
			}
			yy_len >>= 1;
			yyval = ( yy_pv -= yy_len )[1];	/* $$ = $1 */
			yy_state = yypgo[ yy_n = yyr1[ yy_n ] ] +
				*( yy_ps -= yy_len ) + 1;
			if ( yy_state >= YYLAST ||
				yychk[ yy_state = yyact[ yy_state ] ] != -yy_n )
			{
				yy_state = yyact[ yypgo[ yy_n ] ];
			}
		}
					/* save until reenter driver code */
		yystate = yy_state;
		yyps = yy_ps;
		yypv = yy_pv;
	}
	/*
	** code supplied by user is placed in this switch
	*/
	switch( yytmp )
	{
		
case 1:
# line 221 "dcdparse.y"
{ resval =yypvt[-1].valval; return 0; } break;
case 2:
# line 223 "dcdparse.y"
{ asn( yypvt[-3].valval, yypvt[-1].valval); resval = yypvt[-1].valval;  ERRCHK; return 0; } break;
case 3:
# line 225 "dcdparse.y"
{ asn( yypvt[-3].valval, yypvt[-1].valval); resval = yypvt[-1].valval;  ERRCHK; return 0; } break;
case 4:
# line 227 "dcdparse.y"
{
               int result;
            
                result = errcode;
                errcode = 0;
                yyerrok;
                return result;
            } break;
case 5:
# line 240 "dcdparse.y"
{ yyval.valval = yypvt[-1].valval; } break;
case 6:
# line 242 "dcdparse.y"
{ yyval.valval = lstsel(yypvt[-2].valval,yypvt[-0].valval); ERRCHK } break;
case 7:
# line 244 "dcdparse.y"
{ yyval.valval = lst(yypvt[-2].valval,yypvt[-0].valval);if (errcode) { FreeVar(&yyval.valval); YYERROR; } } break;
case 8:
# line 246 "dcdparse.y"
{ yyval.valval = lstn(yypvt[-2].valval,yypvt[-0].valval); ERRCHK } break;
case 9:
# line 248 "dcdparse.y"
{ yyval.valval = binop(add_i, yypvt[-2].valval, yypvt[-0].valval); ERRCHK } break;
case 10:
# line 250 "dcdparse.y"
{ yyval.valval = binop(sub_i, yypvt[-2].valval, yypvt[-0].valval); ERRCHK } break;
case 11:
# line 252 "dcdparse.y"
{ yyval.valval = binop(mul_i, yypvt[-2].valval, yypvt[-0].valval); ERRCHK } break;
case 12:
# line 254 "dcdparse.y"
{ yyval.valval = binop(div_i, yypvt[-2].valval, yypvt[-0].valval); ERRCHK } break;
case 13:
# line 256 "dcdparse.y"
{ yyval.valval = binop(pwr, yypvt[-2].valval, yypvt[-0].valval); ERRCHK } break;
case 14:
# line 258 "dcdparse.y"
{ yyval.valval = fun(yypvt[-3].valval, yypvt[-1].argval); ERRCHK } break;
case 15:
# line 260 "dcdparse.y"
{ yyval.valval = dcd_file(yypvt[-5].valval, yypvt[-3].valval, yypvt[-1].valval); ERRCHK } break;
case 16:
# line 262 "dcdparse.y"
{ yyval.valval = dcd_descr(yypvt[-5].valval, yypvt[-3].valval, yypvt[-1].valval); ERRCHK } break;
case 17:
# line 264 "dcdparse.y"
{ yyval.valval = dcd_descr(yypvt[-3].valval, NullVal, yypvt[-1].valval); ERRCHK } break;
case 18:
# line 266 "dcdparse.y"
{ yyval.valval = dcd_table(yypvt[-9].valval, yypvt[-7].valval, yypvt[-5].valval, yypvt[-3].valval, yypvt[-1].valval); ERRCHK } break;
case 19:
# line 268 "dcdparse.y"
{ yyval.valval = dcd_table(yypvt[-7].valval, NullVal, yypvt[-5].valval, yypvt[-3].valval, yypvt[-1].valval); ERRCHK } break;
case 20:
# line 270 "dcdparse.y"
{ yyval.valval = dcd_image(yypvt[-5].valval, yypvt[-3].valval, yypvt[-1].valval); ERRCHK } break;
case 21:
# line 272 "dcdparse.y"
{ yyval.valval = dcd_image(yypvt[-3].valval, NullVal, yypvt[-1].valval); ERRCHK } break;
case 22:
# line 274 "dcdparse.y"
{ yyval.valval = monop(neg_i, yypvt[-0].valval); ERRCHK } break;
case 23:
# line 276 "dcdparse.y"
{ yyval.valval = yypvt[-0].valval;  } break;
case 24:
# line 278 "dcdparse.y"
{ yyval.valval = var(yypvt[-0].valval); } break;
case 25:
# line 280 "dcdparse.y"
{ yyval.valval = yypvt[-0].valval; } break;
case 26:
# line 282 "dcdparse.y"
{ yyval.valval = yypvt[-0].valval; } break;
case 27:
# line 284 "dcdparse.y"
{ errcode = BADSSYN; ERRCHK } break;
case 28:
# line 286 "dcdparse.y"
{ errcode = FLOVUF; ERRCHK } break;
case 29:
# line 290 "dcdparse.y"
{ yyval.valval =  lsti(yypvt[-2].valval,resval,yypvt[-0].valval); ERRCHK } break;
case 30:
# line 292 "dcdparse.y"
{ yyval.valval =  lsti(yypvt[-0].valval,resval,UnitVal); ERRCHK } break;
case 31:
# line 296 "dcdparse.y"
{ yyval.valval = yypvt[-2].valval; resval = yypvt[-0].valval; } break;
case 32:
# line 299 "dcdparse.y"
{ dcdmode(1);} break;
case 33:
# line 300 "dcdparse.y"
{ yyval.valval = yypvt[-0].valval; dcdmode(0); } break;
case 34:
# line 304 "dcdparse.y"
{ dcdmode(2);} break;
case 35:
# line 305 "dcdparse.y"
{ yyval.valval = yypvt[-0].valval; dcdmode(0); } break;
case 36:
# line 309 "dcdparse.y"
{ arg.num = 1; arg.val[0] = yypvt[-0].valval; yyval.argval = arg; } break;
case 37:
# line 311 "dcdparse.y"
{ if (arg.num>=MAXARG) YYERROR;
              arg.val[arg.num] = yypvt[-0].valval;
              arg.num++;
              yyval.argval = arg; } break;
	}
	goto yystack;		/* reset registers in driver code */
}

# ifdef __RUNTIME_YYMAXDEPTH

static int allocate_stacks() {
	/* allocate the yys and yyv stacks */
	yys = (int *) malloc(yymaxdepth * sizeof(int));
	yyv = (YYSTYPE *) malloc(yymaxdepth * sizeof(YYSTYPE));

	if (yys==0 || yyv==0) {
	   yyerror( (nl_msg(30004,"unable to allocate space for yacc stacks")) );
	   return(1);
	   }
	else return(0);

}


static void free_stacks() {
	if (yys!=0) free((char *) yys);
	if (yyv!=0) free((char *) yyv);
}

# endif  /* defined(__RUNTIME_YYMAXDEPTH) */

#<

#>            dcdscan.c
# include "stdio.h"
#if defined(__cplusplus)
   extern "C" {
#endif
#if (defined(__cplusplus) || defined(__STDC__))
     extern int dcdyyreject();
     extern int dcdyywrap();
     extern int dcdyylook();
     extern int dcdyyback(int *, int);
     extern int dcdyyinput();
     extern void dcdyyoutput(int);
     extern void dcdyyunput(int);
     extern int dcdyylex();
     extern int dcdyyless(int);
#ifdef LEXDEBUG
     extern void allprint();
     extern void sprint();
#endif
#if defined(__cplusplus)
   }
#endif
#endif	/* __cplusplus or __STDC__ */
# define U(x) x
# define NLSTATE dcdyyprevious=YYNEWLINE
# define BEGIN dcdyybgin = dcdyysvec + 1 +
# define INITIAL 0
# define YYLERR dcdyysvec
# define YYSTATE (dcdyyestate-dcdyysvec-1)
# define YYOPTIM 1
# define YYLMAX 200
# define output(c) putc(c,dcdyyout)
# define input() (((dcdyytchar=dcdyysptr>dcdyysbuf?U(*--dcdyysptr):getc(dcdyyin))==10?(dcdyylineno++,dcdyytchar):dcdyytchar)==EOF?0:dcdyytchar)
# define unput(c) {dcdyytchar= (c);if(dcdyytchar=='\n')dcdyylineno--;*dcdyysptr++=dcdyytchar;}
# define dcdyymore() (dcdyymorfg=1)
# define ECHO fprintf(dcdyyout, "%s",dcdyytext)
# define REJECT { nstr = dcdyyreject(); goto dcdyyfussy;}
int dcdyyleng;
int dcdyylenguc;
extern unsigned char dcdyytextarr[];
# ifdef YYCHAR_ARRAY
extern char dcdyytext[];
# else
extern unsigned char dcdyytext[];
# endif
int dcdyyposix_point=0;
int dcdyynls16=0;
int dcdyynls_wchar=0;
char *dcdyylocale = "C C C C C C";
int dcdyymorfg;
extern unsigned char *dcdyysptr, dcdyysbuf[];
int dcdyytchar;
FILE *dcdyyin = {NULL}, *dcdyyout = {NULL};
extern int dcdyylineno;
struct dcdyysvf { 
	int dcdyystoff;
	struct dcdyysvf *dcdyyother;
	int *dcdyystops;};
struct dcdyysvf *dcdyyestate;
extern struct dcdyysvf dcdyysvec[], *dcdyybgin;
/* dcdscan.l
                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

Scanner for numeric user input.
Author: J.P. Terlouw

                                 NOTICE

The current makefiles call lex only on hp9000s700 systems. Other
architectures compile dcdscan.c without calling lex.
*/

#include "stdlib.h"
#include "math.h"
#include "dcdcom.h"
#include "y.tab.h"                /* generated by yacc -d */
#include "varmgr.h"
#include "dcdscan.h"
#undef   input                    /* defined internally */
#undef   unput                    /* defined internally */
#undef   output                   /* defined internally */
#define  output(x)                /* no-op */
#define token(x) return x
#if defined(NULL)
#undef NULL
#endif
#if defined(NULL)
#undef NULL
#endif
#define NULL  NULL
#define NULL NULL

#if 0
static int dcdyywrap(void);
#endif
static int identifier();
static int opchar();
static int number();
static char input(void);
static void unput(char);

static char  *bufptr;
static char  ustack[YYLMAX];
static int   stkptr=0;
static int   brakbal=0;
static int   scanmode=0;

# define MATH 2
# define LITERAL 4
# define GDSLEV 6
# define YYNEWLINE 10
dcdyylex(){
   int nstr; extern int dcdyyprevious;
   while((nstr = dcdyylook()) >= 0)
dcdyyfussy: switch(nstr){
case 0:
   if(dcdyywrap()) return(0); break;
case 1:
 { (void)identifier(); token(STRING); }
break;
case 2:
    { (void)identifier(); token(STRING); }
break;
case 3:
    { token(number()); }
break;
case 4:
           { token(number()); }
break;
case 5:
        { token(DESCR); }
break;
case 6:
        { token(IMAGE); }
break;
case 7:
        { token(TABLE); }
break;
case 8:
           { token(TEXTFILE); }
break;
case 9:
   { token(identifier()); }
break;
case 10:
           { token(opchar()); }
break;
case 11:
                    { token('-'); }
break;
case 12:
                    { token('+'); }
break;
case 13:
             { brakbal++; token('('); }
break;
case 14:
             { brakbal--; token(')'); }
break;
case 15:
           { if (brakbal) token(','); else token(WHITE); }
break;
case 16:
           { token(POWER); }
break;
case 17:
           { token (REPEAT); }
break;
case 18:
                   { token(WHITE); }
break;
case 19:
                       { token('\n');  }
break;
case 20:
                            { token(LEXERR); }
break;
case -1:
break;
default:
   fprintf(dcdyyout,"bad switch dcdyylook %d",nstr);
} return(0); }
/* end of dcdyylex */

#ifndef __cplusplus
static void __dcdyy__unused() { main(); }
#endif

extern void dcdinit(char *text)
{
   bufptr = text;
   while (*bufptr==' ') bufptr++;
   stkptr = 0;
   brakbal= 0;
   BEGIN MATH;
}

extern void dcdmode(int x)
{
   /* ... symbolic modes would be nicer */
   switch (x) {
      case 1:  BEGIN LITERAL; break;
      case 2:  BEGIN GDSLEV;  break;
      default: BEGIN MATH;
   }
   scanmode = x;
}

static char input(void)
{
   if (stkptr) {
      return ustack[--stkptr];
   }
   if (!*(bufptr)) {
      return '\n';
   }
   return *(bufptr++);
}

static void unput(char c)
{
   if (stkptr<YYLMAX) {
      ustack[stkptr++] = c;
   }
}

static int identifier()
{
   dcdyylval.valval = StrVal((char*)dcdyytext);
   if (IsVar((char*)dcdyytext)) return IDENTIFIER; else return STRING;
}

static int opchar()
{
   char *opstring=(char*)dcdyytext;
   while (*opstring==' ') opstring++;
   return (int)*opstring;
}

static int number()
{
   int    i;
   double curval, f, frac, sumlog;
   int    tenp;
   char ch;
   
   dcdyylval.valval.type   = DOUBLE;
   curval = 0.0;
   for (i=0; i<dcdyyleng && isdigit(dcdyytext[i]); i++) {
      curval = 10.0 * curval + dcdyytext[i] - '0';
      if (curval>MAXFLOAT) return BADNUM;
   }
   if (dcdyytext[i]=='.') {
      i++;
      f = 1.0;
      frac = 0;
      for (; i<dcdyyleng && isdigit(dcdyytext[i]); i++) {
         frac = 10.0 * frac + dcdyytext[i] - '0';
         f = 10.0 * f;
         if ((frac > MAXFLOAT)||(f > MAXFLOAT)) return BADNUM;
      }
      curval = curval + frac/f;
   }
   ch = dcdyytext[i];
   if ((curval!=0) && ((ch == 'E')||(ch == 'e')||(ch == 'D')||(ch == 'd'))) {
      i++;
      tenp = 1;
      frac = 0.0;
      if (dcdyytext[i] == '+') {
         i++;
      } else if (dcdyytext[i] == '-') {
         tenp = -tenp;
         i++;
      }
      for (; i<dcdyyleng && isdigit(dcdyytext[i]); i++) {
         frac = 10.0 * frac + dcdyytext[i] - '0';
      }
      frac *= tenp;
      sumlog = log10(curval)+frac;
      if ((MINLOG < sumlog)&&(sumlog < MAXLOG)) {
         curval = curval * pow(10.0,(double) frac);
      } else return BADNUM;
   }
   dcdyylval.valval.value = curval;
   return NUMBER;
}

#if 0
static int dcdyywrap()
#else
extern int dcdyywrap()
#endif
{
   return 1;
}
int dcdyyvstop[] = {
0,

20,
0,

19,
0,

20,
0,

18,
20,
0,

13,
20,
0,

14,
20,
0,

10,
20,
0,

12,
20,
0,

15,
20,
0,

11,
20,
0,

20,
0,

10,
20,
0,

3,
20,
0,

10,
20,
0,

9,
20,
0,

9,
20,
0,

9,
20,
0,

9,
20,
0,

9,
20,
0,

1,
20,
0,

2,
20,
0,

2,
20,
0,

18,
0,

14,
0,

10,
0,

15,
0,

10,
0,

10,
0,

13,
0,

16,
0,

4,
0,

3,
0,

3,
0,

17,
0,

9,
0,

9,
0,

9,
0,

9,
0,

9,
0,

1,
0,

2,
0,

2,
0,

3,
0,

9,
0,

9,
0,

9,
0,

9,
0,

4,
0,

9,
0,

8,
9,
0,

9,
0,

9,
0,

5,
9,
0,

6,
9,
0,

7,
9,
0,
0};
# define YYTYPE unsigned char
struct dcdyywork { YYTYPE verify, advance; } dcdyycrank[] = {
{0,0},	{0,0},	{1,9},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{1,10},	
{11,10},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{1,11},	{11,31},	{13,38},	
{17,35},	{20,36},	{22,36},	{39,39},	
{15,36},	{1,9},	{1,9},	{1,9},	
{1,9},	{1,9},	{1,9},	{1,9},	
{1,9},	{1,9},	{15,39},	{3,9},	
{44,44},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{1,9},	
{3,10},	{0,0},	{0,0},	{0,0},	
{22,44},	{0,0},	{1,9},	{1,9},	
{1,9},	{1,9},	{1,9},	{1,9},	
{1,9},	{1,9},	{1,9},	{24,46},	
{0,0},	{1,9},	{1,9},	{0,0},	
{25,47},	{0,0},	{3,12},	{1,9},	
{1,9},	{1,9},	{0,0},	{0,0},	
{0,0},	{0,0},	{3,13},	{3,14},	
{3,15},	{3,16},	{3,17},	{3,18},	
{3,19},	{3,20},	{3,21},	{0,0},	
{0,0},	{4,20},	{0,0},	{4,20},	
{26,48},	{27,49},	{46,56},	{24,46},	
{3,22},	{40,53},	{40,53},	{3,20},	
{25,47},	{3,20},	{47,57},	{3,23},	
{3,23},	{3,23},	{3,24},	{3,23},	
{3,25},	{3,23},	{3,23},	{3,26},	
{0,0},	{48,58},	{3,23},	{3,23},	
{5,28},	{5,28},	{5,28},	{5,28},	
{3,23},	{3,23},	{3,27},	{4,9},	
{26,48},	{27,49},	{46,56},	{0,0},	
{0,0},	{40,53},	{40,53},	{0,0},	
{0,0},	{3,9},	{47,57},	{49,59},	
{5,28},	{5,28},	{5,28},	{5,28},	
{5,28},	{5,28},	{5,28},	{5,28},	
{5,28},	{48,58},	{56,62},	{5,28},	
{5,28},	{6,28},	{6,28},	{6,28},	
{6,28},	{5,28},	{5,28},	{5,28},	
{19,40},	{19,40},	{19,40},	{19,40},	
{19,40},	{19,40},	{19,40},	{19,40},	
{19,40},	{19,40},	{7,29},	{49,59},	
{0,0},	{6,28},	{6,28},	{6,28},	
{6,28},	{6,28},	{6,28},	{6,28},	
{6,28},	{6,28},	{56,62},	{7,30},	
{6,28},	{6,28},	{7,30},	{57,63},	
{0,0},	{0,0},	{6,28},	{6,28},	
{6,28},	{58,64},	{0,0},	{59,65},	
{7,30},	{62,66},	{64,67},	{65,68},	
{8,29},	{0,0},	{0,0},	{7,30},	
{7,30},	{7,30},	{7,30},	{7,30},	
{7,30},	{7,30},	{7,30},	{7,30},	
{0,0},	{8,30},	{7,30},	{7,30},	
{8,30},	{0,0},	{0,0},	{57,63},	
{7,30},	{7,30},	{7,30},	{0,0},	
{12,10},	{58,64},	{8,30},	{59,65},	
{0,0},	{62,66},	{64,67},	{65,68},	
{0,0},	{8,30},	{8,30},	{8,30},	
{8,30},	{8,30},	{8,30},	{8,30},	
{8,30},	{8,30},	{0,0},	{0,0},	
{8,30},	{8,30},	{12,32},	{0,0},	
{0,0},	{0,0},	{8,30},	{8,30},	
{8,30},	{0,0},	{0,0},	{12,33},	
{12,34},	{0,0},	{12,35},	{0,0},	
{21,41},	{12,36},	{21,42},	{21,42},	
{21,42},	{21,42},	{21,42},	{21,42},	
{21,42},	{21,42},	{21,42},	{21,42},	
{12,37},	{0,0},	{0,0},	{12,36},	
{0,0},	{12,36},	{0,0},	{43,54},	
{0,0},	{43,54},	{21,43},	{21,43},	
{43,55},	{43,55},	{43,55},	{43,55},	
{43,55},	{43,55},	{43,55},	{43,55},	
{43,55},	{43,55},	{54,55},	{54,55},	
{54,55},	{54,55},	{54,55},	{54,55},	
{54,55},	{54,55},	{54,55},	{54,55},	
{0,0},	{0,0},	{0,0},	{12,33},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{21,43},	{21,43},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{23,45},	
{23,45},	{23,45},	{23,45},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{0,0},	{0,0},	{0,0},	
{0,0},	{28,50},	{0,0},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{28,50},	{28,50},	{28,50},	
{28,50},	{29,10},	{53,60},	{0,0},	
{53,60},	{0,0},	{0,0},	{53,61},	
{53,61},	{53,61},	{53,61},	{53,61},	
{53,61},	{53,61},	{53,61},	{53,61},	
{53,61},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{29,51},	
{60,61},	{60,61},	{60,61},	{60,61},	
{60,61},	{60,61},	{60,61},	{60,61},	
{60,61},	{60,61},	{0,0},	{0,0},	
{29,52},	{0,0},	{0,0},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{29,52},	{29,52},	
{29,52},	{29,52},	{30,52},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{30,52},	
{0,0},	{0,0},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{30,52},	{30,52},	{30,52},	
{30,52},	{41,41},	{41,41},	{41,41},	
{41,41},	{41,41},	{41,41},	{41,41},	
{41,41},	{41,41},	{41,41},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{41,43},	{41,43},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{41,43},	{41,43},	{0,0},	
{0,0}};
struct dcdyysvf dcdyysvec[] = {
{0,	0,	0},
{-1,	0,		0},	
{0,	dcdyysvec+1,	0},	
{-50,	0,		0},	
{-40,	dcdyysvec+3,	0},	
{-83,	dcdyysvec+1,	0},	
{-116,	dcdyysvec+1,	0},	
{-146,	dcdyysvec+1,	0},	
{-176,	dcdyysvec+1,	0},	
{0,	0,		dcdyyvstop+1},
{0,	0,		dcdyyvstop+3},
{2,	0,		dcdyyvstop+5},
{222,	0,		dcdyyvstop+7},
{3,	0,		dcdyyvstop+10},
{0,	0,		dcdyyvstop+13},
{8,	0,		dcdyyvstop+16},
{0,	0,		dcdyyvstop+19},
{4,	0,		dcdyyvstop+22},
{0,	0,		dcdyyvstop+25},
{120,	0,		dcdyyvstop+28},
{5,	0,		dcdyyvstop+30},
{222,	0,		dcdyyvstop+33},
{6,	0,		dcdyyvstop+36},
{276,	0,		dcdyyvstop+39},
{6,	dcdyysvec+23,	dcdyyvstop+42},
{7,	dcdyysvec+23,	dcdyyvstop+45},
{27,	dcdyysvec+23,	dcdyyvstop+48},
{40,	dcdyysvec+23,	dcdyyvstop+51},
{354,	0,		dcdyyvstop+54},
{467,	0,		dcdyyvstop+57},
{558,	0,		dcdyyvstop+60},
{0,	dcdyysvec+11,	0},	
{0,	dcdyysvec+12,	dcdyyvstop+63},
{0,	0,		dcdyyvstop+65},
{0,	dcdyysvec+15,	dcdyyvstop+67},
{0,	dcdyysvec+17,	dcdyyvstop+69},
{0,	dcdyysvec+20,	dcdyyvstop+71},
{0,	dcdyysvec+22,	dcdyyvstop+73},
{0,	dcdyysvec+13,	dcdyyvstop+75},
{7,	0,		dcdyyvstop+77},
{41,	dcdyysvec+19,	dcdyyvstop+79},
{633,	0,		dcdyyvstop+81},
{0,	dcdyysvec+21,	dcdyyvstop+83},
{244,	0,		0},	
{20,	0,		dcdyyvstop+85},
{0,	dcdyysvec+23,	dcdyyvstop+87},
{23,	dcdyysvec+23,	dcdyyvstop+89},
{38,	dcdyysvec+23,	dcdyyvstop+91},
{60,	dcdyysvec+23,	dcdyyvstop+93},
{81,	dcdyysvec+23,	dcdyyvstop+95},
{0,	dcdyysvec+28,	dcdyyvstop+97},
{0,	dcdyysvec+29,	dcdyyvstop+99},
{0,	dcdyysvec+30,	dcdyyvstop+101},
{435,	0,		0},	
{254,	0,		0},	
{0,	dcdyysvec+54,	dcdyyvstop+103},
{91,	dcdyysvec+23,	dcdyyvstop+105},
{126,	dcdyysvec+23,	dcdyyvstop+107},
{130,	dcdyysvec+23,	dcdyyvstop+109},
{127,	dcdyysvec+23,	dcdyyvstop+111},
{452,	0,		0},	
{0,	dcdyysvec+60,	dcdyyvstop+113},
{123,	dcdyysvec+23,	dcdyyvstop+115},
{0,	dcdyysvec+23,	dcdyyvstop+117},
{137,	dcdyysvec+23,	dcdyyvstop+120},
{138,	dcdyysvec+23,	dcdyyvstop+122},
{0,	dcdyysvec+23,	dcdyyvstop+124},
{0,	dcdyysvec+23,	dcdyyvstop+127},
{0,	dcdyysvec+23,	dcdyyvstop+130},
{0,	0,	0}};
struct dcdyywork *dcdyytop = dcdyycrank+734;
struct dcdyysvf *dcdyybgin = dcdyysvec+1;
unsigned char dcdyymatch[] = {
00  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,012 ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
040 ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
'(' ,')' ,'*' ,'+' ,',' ,'-' ,'.' ,'/' ,
'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,
'0' ,'0' ,':' ,01  ,01  ,'*' ,01  ,'*' ,
01  ,'A' ,'B' ,'C' ,'D' ,'E' ,'F' ,'G' ,
'H' ,'I' ,'H' ,'H' ,'L' ,'M' ,'H' ,'H' ,
'H' ,'H' ,'R' ,'S' ,'T' ,'H' ,'H' ,'H' ,
'H' ,'H' ,'H' ,'(' ,01  ,')' ,01  ,'.' ,
01  ,'A' ,'B' ,'C' ,'D' ,'E' ,'F' ,'G' ,
'H' ,'I' ,'H' ,'H' ,'L' ,'M' ,'H' ,'H' ,
'H' ,'H' ,'R' ,'S' ,'T' ,'H' ,'H' ,'H' ,
'H' ,'H' ,'H' ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
0};
unsigned char dcdyyextra[] = {
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0};
/* @(#) A.10.32.03 HP C LANGUAGE TOOL (NCFORM) 960517 $      */
int dcdyylineno =1;
# define YYU(x) x
# define NLSTATE dcdyyprevious=YYNEWLINE
 
#ifdef YYNLS16_WCHAR
unsigned char dcdyytextuc[YYLMAX * sizeof(wchar_t)];
# ifdef YY_PCT_POINT /* for %pointer */
wchar_t dcdyytextarr[YYLMAX];
wchar_t *dcdyytext;
# else               /* %array */
wchar_t dcdyytextarr[1];
wchar_t dcdyytext[YYLMAX];
# endif
#else
unsigned char dcdyytextuc;
# ifdef YY_PCT_POINT /* for %pointer */
unsigned char dcdyytextarr[YYLMAX];
unsigned char *dcdyytext;
# else               /* %array */
unsigned char dcdyytextarr[1];
# ifdef YYCHAR_ARRAY
char dcdyytext[YYLMAX];
# else
unsigned char dcdyytext[YYLMAX];
# endif
# endif
#endif

struct dcdyysvf *dcdyylstate [YYLMAX], **dcdyylsp, **dcdyyolsp;
unsigned char dcdyysbuf[YYLMAX];
unsigned char *dcdyysptr = dcdyysbuf;
int *dcdyyfnd;
extern struct dcdyysvf *dcdyyestate;
int dcdyyprevious = YYNEWLINE;
dcdyylook(){
	register struct dcdyysvf *dcdyystate, **lsp;
	register struct dcdyywork *dcdyyt;
	struct dcdyysvf *dcdyyz;
	int dcdyych, dcdyyfirst;
	struct dcdyywork *dcdyyr;
# ifdef LEXDEBUG
	int debug;
# endif
/*	char *dcdyylastch;
 * ***** nls8 ***** */
	unsigned char *dcdyylastch, sec, third, fourth;
	/* start off machines */
# ifdef LEXDEBUG
	debug = 0;
# endif
	dcdyyfirst=1;
	if (!dcdyymorfg)
#ifdef YYNLS16_WCHAR
		dcdyylastch = dcdyytextuc;
#else
# ifdef YYCHAR_ARRAY
		dcdyylastch = (unsigned char *)dcdyytext;
# else
		dcdyylastch = dcdyytext;
# endif
#endif
	else {
		dcdyymorfg=0;
#ifdef YYNLS16_WCHAR
		dcdyylastch = dcdyytextuc+dcdyylenguc;
#else
# ifdef YYCHAR_ARRAY
		dcdyylastch = (unsigned char *)dcdyytext+dcdyyleng;
# else
		dcdyylastch = dcdyytext+dcdyyleng;
# endif
#endif
		}
	for(;;){
		lsp = dcdyylstate;
		dcdyyestate = dcdyystate = dcdyybgin;
		if (dcdyyprevious==YYNEWLINE) dcdyystate++;
		for (;;){
# ifdef LEXDEBUG
			if(debug)fprintf(dcdyyout,"state %d\n",dcdyystate-dcdyysvec-1);
# endif
			dcdyyt = &dcdyycrank[dcdyystate->dcdyystoff];
			if(dcdyyt == dcdyycrank && !dcdyyfirst){  /* may not be any transitions */
				dcdyyz = dcdyystate->dcdyyother;
				if(dcdyyz == 0)break;
				if(dcdyyz->dcdyystoff == 0)break;
				}
			*dcdyylastch++ = dcdyych = input();
			dcdyyfirst=0;
		tryagain:
# ifdef LEXDEBUG
			if(debug){
				fprintf(dcdyyout,"char ");
				allprint(dcdyych);
				putchar('\n');
				}
# endif
			dcdyyr = dcdyyt;
			if ( (int)dcdyyt > (int)dcdyycrank){
				dcdyyt = dcdyyr + dcdyych;
				if (dcdyyt <= dcdyytop && dcdyyt->verify+dcdyysvec == dcdyystate){
					if(dcdyyt->advance+dcdyysvec == YYLERR)	/* error transitions */
						{unput(*--dcdyylastch);break;}
					*lsp++ = dcdyystate = dcdyyt->advance+dcdyysvec;
					goto contin;
					}
				}
# ifdef YYOPTIM
			else if((int)dcdyyt < (int)dcdyycrank) {		/* r < dcdyycrank */
				dcdyyt = dcdyyr = dcdyycrank+(dcdyycrank-dcdyyt);
# ifdef LEXDEBUG
				if(debug)fprintf(dcdyyout,"compressed state\n");
# endif
				dcdyyt = dcdyyt + dcdyych;
				if(dcdyyt <= dcdyytop && dcdyyt->verify+dcdyysvec == dcdyystate){
					if(dcdyyt->advance+dcdyysvec == YYLERR)	/* error transitions */
						{unput(*--dcdyylastch);break;}
					*lsp++ = dcdyystate = dcdyyt->advance+dcdyysvec;
					goto contin;
					}
				dcdyyt = dcdyyr + YYU(dcdyymatch[dcdyych]);
# ifdef LEXDEBUG
				if(debug){
					fprintf(dcdyyout,"try fall back character ");
					allprint(YYU(dcdyymatch[dcdyych]));
					putchar('\n');
					}
# endif
				if(dcdyyt <= dcdyytop && dcdyyt->verify+dcdyysvec == dcdyystate){
					if(dcdyyt->advance+dcdyysvec == YYLERR)	/* error transition */
						{unput(*--dcdyylastch);break;}
					*lsp++ = dcdyystate = dcdyyt->advance+dcdyysvec;
					goto contin;
					}
				}
			if ((dcdyystate = dcdyystate->dcdyyother) && (dcdyyt = &dcdyycrank[dcdyystate->dcdyystoff]) != dcdyycrank){
# ifdef LEXDEBUG
				if(debug)fprintf(dcdyyout,"fall back to state %d\n",dcdyystate-dcdyysvec-1);
# endif
				goto tryagain;
				}
# endif
			else
				{unput(*--dcdyylastch);break;}
		contin:
# ifdef LEXDEBUG
			if(debug){
				fprintf(dcdyyout,"state %d char ",dcdyystate-dcdyysvec-1);
				allprint(dcdyych);
				putchar('\n');
				}
# endif
			;
			}
# ifdef LEXDEBUG
		if(debug){
			fprintf(dcdyyout,"stopped at %d with ",*(lsp-1)-dcdyysvec-1);
			allprint(dcdyych);
			putchar('\n');
			}
# endif
		while (lsp-- > dcdyylstate){
			*dcdyylastch-- = 0;
			if (*lsp != 0 && (dcdyyfnd= (*lsp)->dcdyystops) && *dcdyyfnd > 0){
				dcdyyolsp = lsp;
				if(dcdyyextra[*dcdyyfnd]){		/* must backup */
					while(dcdyyback((*lsp)->dcdyystops,-*dcdyyfnd) != 1 && lsp > dcdyylstate){
						lsp--;
						unput(*dcdyylastch--);
						}
					}
				dcdyyprevious = YYU(*dcdyylastch);
				dcdyylsp = lsp;
#ifdef YYNLS16_WCHAR
				dcdyylenguc = dcdyylastch-dcdyytextuc+1;
				dcdyytextuc[dcdyylenguc] = 0;
#else
# ifdef YYCHAR_ARRAY
				dcdyyleng = dcdyylastch-(unsigned char*)dcdyytext+1;
# else
				dcdyyleng = dcdyylastch-dcdyytext+1;
# endif
				dcdyytext[dcdyyleng] = 0;
#endif
# ifdef LEXDEBUG
				if(debug){
					fprintf(dcdyyout,"\nmatch ");
#ifdef YYNLS16_WCHAR
					sprint(dcdyytextuc);
#else
					sprint(dcdyytext);
#endif
					fprintf(dcdyyout," action %d\n",*dcdyyfnd);
					}
# endif
				return(*dcdyyfnd++);
				}
			unput(*dcdyylastch);
			}
#ifdef YYNLS16_WCHAR
		if (dcdyytextuc[0] == 0  /* && feof(dcdyyin) */)
#else
		if (dcdyytext[0] == 0  /* && feof(dcdyyin) */)
#endif
			{
			dcdyysptr=dcdyysbuf;
			return(0);
			}
#ifdef YYNLS16_WCHAR
		dcdyyprevious = dcdyytextuc[0] = input();
#else
		dcdyyprevious = dcdyytext[0] = input();
#endif
		if (dcdyyprevious>0) {
			output(dcdyyprevious);
#ifdef YYNLS16
                        if (dcdyynls16) {
			int noBytes;
                        sec = input();
                        third = input();
                        fourth = input();
#ifdef YYNLS16_WCHAR
                        noBytes = MultiByte(dcdyytextuc[0],sec,third,fourth);
#else 
                        noBytes = MultiByte(dcdyytext[0],sec,third,fourth);
#endif          
     					switch(noBytes) {
     					case 2:
#ifdef YYNLS16_WCHAR
 						output(dcdyyprevious=dcdyytextuc[0]=sec);
#else
 						output(dcdyyprevious=dcdyytext[0]=sec);
#endif
                                                 unput(fourth);
                                                 unput(third);
                                                 break;
     					case 3:
#ifdef YYNLS16_WCHAR
 						output(dcdyyprevious=dcdyytextuc[0]=sec);
 						output(dcdyyprevious=dcdyytextuc[0]=third);
#else
 						output(dcdyyprevious=dcdyytext[0]=sec);
 						output(dcdyyprevious=dcdyytext[0]=third);
#endif
                                                 unput(fourth);
                                                 break; 
                                         case 4:
#ifdef YYNLS16_WCHAR
 						output(dcdyyprevious=dcdyytextuc[0]=sec);
 						output(dcdyyprevious=dcdyytextuc[0]=third);
 						output(dcdyyprevious=dcdyytextuc[0]=fourth);
#else
 						output(dcdyyprevious=dcdyytext[0]=sec);
 						output(dcdyyprevious=dcdyytext[0]=third);
 						output(dcdyyprevious=dcdyytext[0]=fourth);
#endif
                                                 break;                                                                                            
					default:
					        unput(fourth);
					        unput(third);
						unput(sec);
						break;
						}
					}
#endif
                }
#ifdef YYNLS16_WCHAR
		dcdyylastch=dcdyytextuc;
#else
# ifdef YYCHAR_ARRAY
		dcdyylastch=(unsigned char*)dcdyytext;
# else
		dcdyylastch=dcdyytext;
# endif
#endif
# ifdef LEXDEBUG
		if(debug)putchar('\n');
# endif
		}
	}

# ifdef __cplusplus
dcdyyback(int *p, int m)
# else
dcdyyback(p, m)
	int *p;
# endif
{
if (p==0) return(0);
while (*p)
	{
	if (*p++ == m)
		return(1);
	}
return(0);
}
	/* the following are only used in the lex library */
dcdyyinput(){
	return(input());
	
	}

#if (defined(__cplusplus) || defined(__STDC__))
void dcdyyoutput(int c)
#else
dcdyyoutput(c)
  int c;
# endif
{
	output(c);
}

#if (defined(__cplusplus) || defined(__STDC__))
void dcdyyunput(int c)
#else
dcdyyunput(c)
   int c;
#endif
{
	unput(c);
}
#<

#>            dcdrange.c
/*  dcdrange.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes.
Author: J.P. Terlouw
*/

#include "stdlib.h"
#include "dcdrange.h"

/* ========================================================================== */
/*                             dcdrange                                       */
/* -------------------------------------------------------------------------- */
/*  dcdrange() decodes a string specifying a range.
 *  The range string can have one of the following formats (n and m are
 *  decimal numbers):
 *
 *     empty string ( return value 0 )
 *     n:m          ( return value 3 )
 *     n:           ( return value 1 )
 *     :m           ( return value 2 )
 *     n            ( equivalent to n:n; return value 3 )
 *
 *  If there is an error in the string, i.e. when it does not conform to
 *  one of the formats above, -1 is returned.
 *  If any element is not specified in the string, the corresponding
 *  argument is not changed.
 */
int dcdrange(char *string, int *start, int *end)
{
   long value;
   char *eptr;
   int  n = 0;

   while (*string==' ') string++;           /* skip leading blanks */

   if (!*string) return 0;
   if (*string!=':') {
      value = strtol(string, &eptr, 10);
      while (*eptr==' ') eptr++;            /* skip blanks */
      if (*eptr && *eptr!=':') return -1;   /* bad character encountered? */
      string = eptr;
      *start = value;
      n += 1;
      if (!*eptr) {
         /* one value means range with same start and end */
         *end = *start;
         n += 2;
         return n;
      }
   }

   string++;                         /* skip colon */
   while (*string==' ') string++;
   if (*string) {
      value = strtol(string, &eptr, 10);
      while (*eptr==' ') eptr++;
      if (*eptr) return -1;         /* bad character or illegal trailers? */
      *end = value;
      n += 2;
   }
   return n;
}
#<

#>            editfile.c
/* editfile.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It is responsible for editing a
user-specified file in tHermes.

Author: J.P. Terlouw
*/

#define EDT_NAMELEN 80

#include "stddef.h"
#include "stdlib.h"
#include "string.h"
#include "thermescom.h"
#include "alloc.h"
#include "prompter.h"
#include "monopoly.h"
#include "editfile.h"

typedef struct _Callback {
   struct _Callback *next;
   EditProc proc;
   void *arg;
} *Callback, _Callback;

static char *editor;
static char *current;
static char lastfile[EDT_NAMELEN]="";
static Callback cblist=NULL;

static int  startedit(long,int,char*,void*);
static int  doedit(long,int,char*,void*);
static void edited(int);
static void CallEditProc(char*,int);

extern int LINES, COLS, NTASK;
static char wwwhelp[]="*hermes/unixshell.html#editor";

/* ========================================================================== */
/*                               EditFile                                     */
/* -------------------------------------------------------------------------- */
/*  EditFile prompts the user for the name of a file to be edited.
 *  It returns zero if there are no errors in this stage, non-zero otherwise.
 */
extern int EditFile(char *filename)
{
   char *dfltname;

   if (filename) dfltname = filename; else dfltname = lastfile;
   editor = getenv("EDITOR");
   if (!editor) {
      if (!filename)
         FlashText(0,0," Cannot find editor ",3);
      return EDT_STARTFAIL;
   }
   if (!filename)
      SetPrompterHelp(
         Prompter("Name of file to be edited:          ", dfltname,
                   startedit, (void*)1, 0,0),
         wwwhelp
      );
   else
      SetPrompterHelp(
         Prompter("Edit file?", "Yes", doedit, dfltname, LINES-NUCA-3,COLS-30),
         wwwhelp
      );
   return 0;
}

/* ========================================================================== */
/*                              InsertEditProc                                */
/* -------------------------------------------------------------------------- */
/*  InsertEditProc() registers a callback to be called whenever an edit
 *  operation completes.
 */
extern long InsertEditProc(EditProc proc, void *arg)
{
   Callback cb=New(_Callback);

   cb->proc = proc;
   cb->arg  = arg;
   cb->next = cblist;
   cblist   = cb;
   return (long)cb;
}
/* ========================================================================== */
/*                              RemoveEditProc                                */
/* -------------------------------------------------------------------------- */
/*  RemoveEditProc() removes a callback.
 */
extern void RemoveEditProc(long id)
{
   ((Callback)id)->proc = NULL;    /* block will be released by CallEditProc */
}

/* -------------------------------------------------------------------------- */
/*                               CallEditProc                                 */
/* -------------------------------------------------------------------------- */
/* CallEditProc() calls all registered edit callbacks;
 */
static void CallEditProc(char *filename, int status)
{
   Callback cb, *i, *next;

   for (i=&cblist; *i; i=next) {
      cb = *i;
      next = &cb->next;
      if (cb->proc) {
         (*cb->proc)((long)cb, filename, status, cb->arg);
      } else {
         /* invalidated block: release */
         next = i;  *i = cb->next;  Free(cb);
      }
   }
}

/* -------------------------------------------------------------------------- */
/*                                 startedit                                  */
/* -------------------------------------------------------------------------- */
/*  startedit() is called by prompter and starts the editor.
 */
static int startedit(long id, int status, char *filename, void *store)
{
   if (status==PROMPT_CANCEL) {                    /* user cancelled prompter */
      CallEditProc(filename,EDT_CANCEL);
      return 0;
   }
   current = Malloc(strlen(filename)+1);
   strcpy(current,filename);
   if (store) strcpy(lastfile,filename);
   Monopoly(editor,filename,edited);
   return 0;
}

/* -------------------------------------------------------------------------- */
/*                                  doedit                                    */
/* -------------------------------------------------------------------------- */
/*  doedit is called by Prompter and calls startedit to do the real edit.
 *  The purpose of this intermediate step is to force the filename in the
 *  call to be used.
 */
static int doedit(long id, int status, char *reply, void *filename)
{
   if (status==PROMPT_ENTER && *reply!='Y' && *reply!='y') 
      status = PROMPT_CANCEL;
   return startedit(id, status, (char*)filename, NULL);
}

/* -------------------------------------------------------------------------- */
/*                                   edited                                   */
/* -------------------------------------------------------------------------- */
/*  edited() is called by Monopoly after the editor subprocess has exited.
 *  If necessary it displays an error message.
 *  Finally it calls any registered callbacks.
 */
static void edited(int status)
{
   if (status) FlashText(0,0," Editor exited with error status ",3);
   CallEditProc(current,status);
   Free(current);
}
#<

#>            editkeys.c
/* editkeys.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It is responsible for editing the set of
user input parameters of a task in tHermes.
*/

#include "stddef.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "stdio.h"
#include "thermescom.h"
#include "prompter.h"
#include "keyboard.h"
#include "keysym.h"
#include "parse.h"
#include "editkeys.h"

static char keyfile[NAMLEN+4+1];
static char current[NAMLEN+1]="";
static char *editor;
static long kid;

static void edited(int);
static int  cancel(long,int,char*,void*);
static int  noctrlc(long,int,void*);

/* ========================================================================== */
/*                               EditKeys                                     */
/* -------------------------------------------------------------------------- */
/*  EditKeys starts the keyword editor for the given task.
 *  It returns zero if the editor process was started,
 *  non-zero in case of errors.
 */
extern int EditKeys(char *taskname)
{

   if (*current) return -1;                             /* only one instance */
   editor = getenv("EDITOR");
   if (!editor) {
      FlashText(0,0," Environment variable EDITOR not defined ",3);
      return -2;
   }
   strcpy(current,taskname);
   strcpy(keyfile,current);
   strcat(keyfile,".key");
   (void)write_par(current,keyfile);
   Monopoly(editor,keyfile,edited);
   return 0;
}

/* -------------------------------------------------------------------------- */
/*                                   edited                                   */
/* -------------------------------------------------------------------------- */
/*  edited() is called by Monopoly after the editor subprocess has exited.
 *  It checks the return code and processes the new file contents.
 */
static void edited(int status)
{
   char *keyfcont, *key, *value;
   static struct _TaskStatus itask;
   TaskStatus    task;
   int  size;
   long cmdid;

   if (status) {
      FlashText(0,0," Please check environment variable EDITOR ",3);
      remove(keyfile);
      *current = '\0';
      return;
   }
   task = NameToTask(current);
   if (!task) task = &itask;
   strcpy(itask.name,current);
   keyfcont = FileContents(keyfile,&size);
   task->keep = 0;
   if (keyfcont && ParseCmd(keyfcont)==NOTSKNAME) {
      cmdid = CmdGetInfo();
      init_par(task);
      for (;;) {
         CmdParam(cmdid,&key,&value);
         if (!key) break;
         insert_par(task, key, value, 0);
      }
      CmdDelete(cmdid);
      remove(keyfile);
      *current = '\0';
   } else if (size==0) {
      init_par(task);
      remove(keyfile);
      *current = '\0';
   } else {
      Prompter("Error in edited keywords - cancel edit?", "No",
               cancel, NULL, 0,0);
      kid = InsertKeyboardProc(noctrlc,NULL);        /* force user to respond */
   }
   if (keyfcont) Delete(keyfcont);
}

/* -------------------------------------------------------------------------- */
/*                                  cancel                                    */
/* -------------------------------------------------------------------------- */
/*  cancel() is called by prompter and either cancels the edit or re-starts
 *  the editor.
 */
static int cancel(long id, int status,  char *reply, void *arg)
{
   RemoveKeyboardProc(kid);
   if (*reply=='y' || *reply=='Y') {
      remove(keyfile);
      *current = '\0';
   } else {
      Monopoly(editor,keyfile,edited);
   }
   return 0;
}

/* -------------------------------------------------------------------------- */
/*                                  noctrlc                                   */
/* -------------------------------------------------------------------------- */
/*  keyboard handler to prevent prompter to be aborted or rescheduled.
 */
static int noctrlc(long id, int c, void* arg)
{
   if (c==CTRL_C || c==QRESCHED || c==XRESCHED) {
      Beep();
      return 0;
   }
   return c;
}
#<

#>            execpath.c
/* execpath.c
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It is responsible for the task path and
for finding the executable files.
Author: J.P. Terlouw.
*/

#include "stddef.h"
#include "string.h"
#include "stdlib.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <unistd.h>
#if	defined(__linux__) && !defined(PATH_MAX)
#include	<linux/limits.h>
#endif
#if	defined(__aix__) && !defined(PATH_MAX)
#undef	_H_LIMITS
#define	_POSIX_SOURCE
#ifdef	PAGESIZE
#undef	PAGESIZE
#endif
#include	<sys/limits.h>
#endif
#include "execpath.h"

#define TSKPATHLEN 100
#define MAXTSKLEN  32
#define GIPEXE    "$gip_exe $gip_tsk"

char *rindex(const char *, int);

static char pathname[MAXPATHLEN];
static char path[TSKPATHLEN]=". ";

/* ========================================================================== */
/*                             SetExecPath                                    */
/* -------------------------------------------------------------------------- */
/*  SetExecPath() sets the 'path' to be used for executables.
 *  As argument it accepts a zero-terminated string containing directory
 *  specifications separated by blanks. These spefications may also be
 *  environment variables (prefixed by a dollar sign).
 *  Internally the environment variable "$gip_exe" and "$gip_tsk" are appended
 *  to the path.
 *  On success, SetExecPath() returns zero, on failure, i.e. when the supplied
 *  path string is too long, -1.
 */
extern int SetExecPath(char *newpath)
{
   if (!newpath) return 0;
   if (strlen(newpath)<TSKPATHLEN) {
      strcpy(path,newpath);                           /* user specified path */
      return 0;
   } else return -1;
}

/* ========================================================================== */
/*                             GetExecPath                                    */
/* -------------------------------------------------------------------------- */
/*  GetExecPath() returns a pointer to the character string containing the
 *  current path.
 */
extern char *GetExecPath()
{
   return path;
}

/* ========================================================================== */
/*                              FindExec                                      */
/* -------------------------------------------------------------------------- */
/*  FindExec() translates a supplied executable name to a name containing
 *  a full path, using the path specification set by SetExecPath().
 *  If SetExecPath() has not been called before the current path contains
 *  the current directory followed by Gipsy's standard executable path.
 *  If the translation is successful, a pointer to the translated name is
 *  returned, if it fails NULL is returned.
 */
extern char *FindExec(char *exec)
{
   static char newexec[TSKPATHLEN+MAXTSKLEN];
   char subpath[TSKPATHLEN];
   struct stat statbuf;
   char   ipath[TSKPATHLEN+sizeof(GIPEXE)];
   char  *cpath, *ctask;
   int i=0;

   /* explicit path already given: try only this */
   if (rindex(exec,'/')) {
      cpath = exec;
      ctask = NULL;
   } else {
      cpath = path;
      ctask = exec;
   }

   strcpy(ipath,cpath); 
   if (ctask) {
      strcat(ipath," "); 
      strcat(ipath,GIPEXE);
   }
   do {
      int sb=0, se=0, j=0;
      char *envstr;

      while (ipath[i] && ipath[i]==' ') i++;                   /* skip blanks */
      while (ipath[i] && ipath[i]!=' ') subpath[j++] = ipath[i++];
      subpath[j] = 0; j = 0;
      while (subpath[j]) {
         for (; subpath[j]; j++) {
            newexec[se++] = subpath[j];
            if (subpath[j]=='/') sb = se;
            if (subpath[j+1]=='/') {
               j++;
               break;
            }
         }
         newexec[se] = 0;
         if (newexec[sb]=='$') {
            envstr = getenv(newexec+sb+1);
            if (envstr) {
               strcpy(newexec+sb,envstr);
               se = sb+strlen(envstr);
            } else return NULL;
         }
         sb = se;
      }
      if (!strcmp(newexec,".")) {
         strcpy(newexec,getcwd(pathname, MAXPATHLEN));
      }
      if (ctask) {
         strcat(newexec,"/");
         strcat(newexec,ctask);
      }
      if (!stat(newexec,&statbuf) && (statbuf.st_mode&0111)
                                  &&!(statbuf.st_mode&S_IFDIR)) return newexec;
      else {
         strcat(newexec,".col");
         if (!stat(newexec,&statbuf)) return newexec;
      }
   } while (ipath[i]);
   return NULL;
}
#<

#>            filecontents.c
/*  filecontents.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes.

*/
#include "stddef.h"
#include <sys/file.h>
#ifndef	O_CREAT
#include <fcntl.h>
#endif
#if defined(__sysv__) | defined(__APPLE__)
#include <unistd.h>
#endif
#ifndef	L_XTND
#define	L_XTND	SEEK_END
#endif
#include "filecontents.h"
#include "alloc.h"

/* ========================================================================== */
/*                             FileContents                                   */
/* -------------------------------------------------------------------------- */
/*  FileContents returns a character pointer to a dynamic block of data
 *  containing a copy of the contents of the file designated by 'path'.
 *  If the file cannot be opened or cannot be accessed or has length
 *  zero, a null pointer is returned. Through the argument 'fsize' the
 *  size of the file in bytes is returned, or -1 if the file could not
 *  be opened.
 *  It is the responsibility of the caller to release the memory.
 */
extern char *FileContents(char *path, int *fsize)
{
   int fd = open(path,O_RDONLY,NULL);
   char *contents;
   int  size, nread=0;

   if (fd<0) {
      *fsize = -1;
      return NULL;               /* error: could not open */
   }
   size = lseek(fd,0,L_XTND);    /* determine file length */
   *fsize = size;
   if (size<=0) return NULL;     /* error: zero length or couldn't access */
   (void)lseek(fd,0,L_SET);      /* go back to beginning of file */
   contents = (char *)Malloc(size+1);  /* allocate space + trailer byte */
   while (nread<size) nread += read(fd,contents+nread,size-nread);
   contents[nread] = 0;
   close(fd);
   return contents;
}
#<

#>            findstring.c
/* findstring.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It implements a backward-forward
case-insensitive string search.

Author: J.P. Terlouw
*/

#include "stddef.h"
#include "string.h"
#include "alloc.h"
#include "changecase.h"
#include "findstring.h"

typedef unsigned char khar;

static khar coltab[128]={
'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
'\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
'\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
'\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
'\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047',
'\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057',
'\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067',
'\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077',
'\100', '\101', '\102', '\103', '\104', '\105', '\106', '\107',
'\110', '\111', '\112', '\113', '\114', '\115', '\116', '\117',
'\120', '\121', '\122', '\123', '\124', '\125', '\126', '\127',
'\130', '\131', '\132', '\133', '\134', '\135', '\136', '\137',
'\140', '\101', '\102', '\103', '\104', '\105', '\106', '\107',
'\110', '\111', '\112', '\113', '\114', '\115', '\116', '\117',
'\120', '\121', '\122', '\123', '\124', '\125', '\126', '\127',
'\130', '\131', '\132', '\173', '\174', '\175', '\176', '\177'};

/* ========================================================================== */
/*                               findstring                                   */
/* -------------------------------------------------------------------------- */
/*  findstring performs a case-insensitive string search.
 *
 *  Arguments:  block  -  character pointer to text block;
 *              start  -  pointer to place where to start search;
 *              string -  the string to search for;
 *              backw  -  flag indicating whether to search backwards.
 */
char *findstring(char *block, char *start, char *string, int backw)
{
   khar *ublock =(khar*)block;
   khar *ustart =(khar*)start;
   khar *ustring=(khar*)string;
   khar *tmp, *result;
   int  len=strlen(ustring);
   int  i;
   int cins=1; /* case-insensitive flag (not used now: always true) */

   /* first check whether search is possible at all */
   if (backw) {
      if (ublock>(ustart-len)) return NULL;
   } else {
      for (i=0; i<len; i++) {
         if (ustart[i]==0) return NULL;
      }
   }

   if (cins) {
      tmp = (khar*)Malloc(len+1);
      strcpy(tmp,ustring);
      ustring = tmp;
      StringUp((char*)ustring);
   }

   if (backw) {
      ustart -= len;
      for (;;) {
         for (i=0; i<len; i++) if (coltab[ustart[i]]!=ustring[i]) break;
         if (i==len) {
            result = ustart;
            break;
         }
         if (ustart==ublock) {
            result = NULL;
            break;
         }
         ustart--;
      }
   } else {
      for (;;) {
         for (i=0; i<len; i++) if (coltab[ustart[i]]!=ustring[i]) break;
         if (i==len) {
            result = ustart;
            break;
         }
         if (ustart[len]==0) {
            result = NULL;
            break;
         }
         ustart++;
      }
   }
   if (cins) Free(ustring);
   return (char*)result;
}

#<

#>            getdtablesize.c
/*
 *  Get descriptor table size.
 *  This routine is only used for systems where it is not available
 *  as a system call.
 */
int getdtablesize(void)
{
   return 64;
}
#<

#>            help.c
/* help.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It handles the tHermes context-sensitive
help display.

Author: J.P. Terlouw.
*/

#include "stddef.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "stdio.h"
#include "thermescom.h"
#include "windows.h"
#include "prompter.h"
#include "keyboard.h"
#include "keysym.h"
#include "ucamgr.h"
#include "taskmgr.h"
#include "findstring.h"
#include "helpkey.h"
#include "webhelp.h"
#include "help.h"

#define NALLOC  100  /* memory allocation increment (in lines) */
#define OVERLAP   2  /* number of lines overlap between pages  */

static Window win=NULL,top=NULL;
static int    xsize, ysize;
static int    textsize, nlines, startline;
static long   kid=0, uid=0, tid=0;
static int    keywindow=0,keyhelpmode=1;
static char   help_name[NAMLEN+1];
static char   help_key[KEYLEN+1];
static char   seastr[40];
static char   *text=NULL;

static int  search(long,int,char*,void*);
static void deactivate(void);
static int  getchars(long,int,void*);
static void followuca(long,int,void*);
static void tasknotify(long,TaskStatus,void*);
static void updatewin(void);
static int  findtext(char*);
static char *findhelpname(void);
static void initkeyhelp(void);

extern int NTASK;
extern int LINES, COLS;                      /* screen dimensions from Curses */

/* ========================================================================== */
/*                               InitHelp                                     */
/* -------------------------------------------------------------------------- */
/*  InitHelp initializes the context-sensitive help display.
 *  It displays the .dc1 document of the task of which the name is present
 *  in the User Command Area. If a user input keyword is present, the
 *  document is positioned at the first occurrence of the keyword.
 */
extern void InitHelp()
{
   Window errwin;

   int i, nchars;
   char topline[80];
   char filename[80];
   char dc;
   FILE *stream;

   if (kid) return;                                /* allow only one instance */

   xsize = COLS;
   ysize = LINES-NUCA-NTASK-1;
   strcpy(help_name,findhelpname());
   strcpy(filename,getenv("gip_tsk"));
   strcat(filename,"/");
   strcat(filename,help_name);
   strcat(filename,".dc1");
   if ((stream=fopen(filename,"r"))) {
      if (!WebHelp(filename)) {
         (void)fclose(stream);
         return;                                  /* delegated to WWW browser */
      }
      text = (char*)Malloc(NALLOC*xsize+1);
      textsize = NALLOC;
      i = NALLOC;
      nlines = 0;
      nchars = 0;
      while (!feof(stream)) {
         if (i==0) {
            textsize += NALLOC;
            i = NALLOC;
            text = (char*)Realloc(text,textsize*xsize+1);
         }
         dc = fgetc(stream);
         if (dc==EOF || dc=='\n') {
            while ((nchars)<xsize) {
               *(text+nlines*xsize+nchars) = ' ';
               nchars++;
            }
            nlines++;
            i--;
            nchars = 0;
         } else if (nchars<xsize) {
            *(text+nlines*xsize+nchars++) = dc;
         }
      }
      *(text+nlines*xsize+nchars) = 0;
      (void)fclose(stream);
   } else {
      errwin = CreateWindow(4,25);
      FrameWindow(errwin);
      PutLine(errwin,1," Document not found:");
      PutString(errwin,2,3,help_name);
      PutString(errwin,2,3+strlen(help_name),".dc1");
      MapWindow(errwin,0,0);
      InsertTimerProc(TimeDelWin,3,errwin);
      errwin = NULL;
      Beep();
      return;
   }
   sprintf(topline,
   "  Document: %s.dc1  ---  Remove: TAB  ---  Hyper Help: ESC X  ",help_name);
   if (!win) {
      /* windows are re-used; not deleted */
      top = CreateWindow(1,xsize);
      top->framed = TRUE;
      win = CreateWindow(1,xsize);
      free(win->backup);                /* maintain own backup */
      win->private = FALSE;
   }
   for (i=0; i<xsize; i++) PutChar(top,0,i,'=');
   PutString(top,0,(xsize-strlen(topline))/2,topline);
   MapWindow(top,0,0);
   win->ny = ysize<nlines-1?ysize:nlines-1;               /* resize window */
   startline = 0;
   findtext(KeyFromUca());
   updatewin();
   MapWindow(win,1,0);
   kid = InsertKeyboardProc(getchars,NULL);
   uid = InsertUcaProc(followuca,NULL);
   tid = InsertTaskProc(tasknotify,NULL);
   initkeyhelp();
}

/* ========================================================================== */
/*                               SetKeyHelp                                   */
/* -------------------------------------------------------------------------- */
/*  SetKeyHelp() disables or enables the keyword help overlay.
 *  mode = 1 enables the overlay
 *  mode = 0 disables the overlay.
 */
extern void SetKeyHelp(int mode)
{
   keyhelpmode = mode;
}

/* -------------------------------------------------------------------------- */
/*                               getchars                                     */
/* -------------------------------------------------------------------------- */
/*  Keyboard procedure getchars() allows the user to page through the document.
 */
static int  getchars(long id, int c, void* arg)
{
   static int sdir;

   if (c==QRESCHED) return c;  /* allow rescheduling but do not participate */

   if ((c==CTRL_I)||(c==CTRL_P)) {
      deactivate();
      return 0;
   }

   if (win->ny>=ysize) {
      sdir = 0;
      switch (c) {
         case ESCBIT|'<': startline = 0; break;

         case ESCBIT|'>': startline = nlines-ysize; break;
          
         case ESCBIT|TAB: SetKeyHelp(0);
                          if (keywindow) {
                             StopHelpKey(); keywindow=0; break;
                          } else return c;

         case CTRL_Z:  startline = startline - ysize + OVERLAP;
                       break;

         case CTRL_V:  startline = startline + ysize - OVERLAP;
                       break;

         case CTRL_R:  sdir = 1;              /* switch to backward search */
         case CTRL_S:
                       Prompter("Enter search string:",seastr,
                       search, (void*)sdir, LINES-6,40);
                       break;

         default:      return c;                /* propagate              */
      }
   } else return c;
   updatewin();
   return 0;
}

/* -------------------------------------------------------------------------- */
/*                             followuca                                      */
/* -------------------------------------------------------------------------- */
/*  UCA callback procedure followuca repositions the document at any keyword
 *  present in the User Command Area.
 */
static void followuca(long id, int reason, void *arg)
{
   if (   reason==UCA_PROMPT
       && !strcmp(help_name,findhelpname())
       && !findtext(KeyFromUca())
       && nlines>ysize ) updatewin();
   initkeyhelp();
}

/* -------------------------------------------------------------------------- */
/*                             tasknotify                                     */
/* -------------------------------------------------------------------------- */
/*  tasknotify() deactivates the help screen if the task asscociated with it
 *  is terminated
 */
static void tasknotify(long id, TaskStatus task, void *arg)
{
   char taskname[NAMLEN+1];

   if (!task->proc) {
      strcpy(taskname,task->name);
      StringLow(taskname);
      if (!strcmp(taskname,help_name)) deactivate();
   }
}

/* -------------------------------------------------------------------------- */
/*                                 search                                     */
/* -------------------------------------------------------------------------- */
/*   search() performs a text search in the help display.
 */
static int search(long id, int status, char *result, void *arg)
{
   char *matchpoint;
   int sdir=(int)arg;

   if (status==PROMPT_CANCEL) return 0;
   strcpy(seastr,result);
   matchpoint = findstring(text, text+(startline+1-sdir)*xsize, seastr, sdir);
   if (matchpoint) {
      startline = (matchpoint-text)/xsize;
      updatewin();
      return 0;
   } else {
      SetPrompterError(id,"- text not found");
      return -1;
   }
}

/* -------------------------------------------------------------------------- */
/*                           findhelpname                                     */
/* -------------------------------------------------------------------------- */
/*   findhelpname() determines the name of the appropriate .dc1 document
 */
static char *findhelpname(void)
{
   char *hn;
   TaskStatus task=NULL;
   static char name[NAMLEN+1];

   hn = NameFromUca();
   if (hn && *hn) strcpy(name,hn); else strcpy(name,"thelp");
   StringUp(name);
   task = NameToTask(name);
   if (task) {
      ProcStatus proc;
      for (proc=task->proc; proc->next; proc=proc->next) {;}
      strcpy(name,proc->name);
   }
   StringLow(name);
   return name;
}

/* -------------------------------------------------------------------------- */
/*                             deactivate                                     */
/* -------------------------------------------------------------------------- */
/*  deactivate() removes the help display and cleans up everything.
 */
static void deactivate(void)
{
   UnmapWindow(top);
   UnmapWindow(win);
   free(text);
   help_key[0] = 0;
   RemoveKeyboardProc(kid); kid = 0;
   RemoveUcaProc(uid); uid = 0;
   RemoveTaskProc(tid); tid = 0;
   if (keywindow) StopHelpKey();
   keywindow = 0;
}

/* -------------------------------------------------------------------------- */
/*                             updatewin                                      */
/* -------------------------------------------------------------------------- */
/*  updatewin() maps the correct part of the memory representation of
 *  the document to the help window.
 */
static void updatewin(void)
{
   if (startline>(nlines-ysize)) startline = nlines-ysize;
   if (startline<0) startline = 0;
   win->backup = text+startline*xsize;
   RedrawWindow(win);
}

/* -------------------------------------------------------------------------- */
/*                              findtext                                      */
/* -------------------------------------------------------------------------- */
/*  findtext() searches for the text specified in the argument and adjusts
 *  the point where the memory is to be mapped to the help window.
 */
static int findtext(char *word)
{
   char *matchpoint;

   if (nlines>ysize && strcmp(help_key,word)) {
      matchpoint = strstr(text,word);
      if (matchpoint) {
         startline = (matchpoint-text)/xsize;
         strcpy(help_key,word);
         return 0;        /* found and startline adjusted */
      } else return -1;   /* string not found */
   } else return -1;      /* fits on one page or found on a previous occasion */
}

/* -------------------------------------------------------------------------- */
/*                             initkeyhelp                                    */
/* -------------------------------------------------------------------------- */
static void initkeyhelp(void)
{
   if (keyhelpmode && !keywindow && *KeyFromUca()) {
      StringUp(help_name);
      HelpKey(help_name);
      StringLow(help_name);
      keywindow = 1;
   }
}
#<

#>            helpkey.c
/* helpkey.c
                              COPYRIGHT (c) 1993
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It handles the tHermes context-sensitive
help display associated with userinp keywords.

Author: J.P. Terlouw.
*/

#include "stddef.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "stdio.h"
#include "thermescom.h"
#include "windows.h"
#include "keyboard.h"
#include "keysym.h"
#include "ucamgr.h"
#include "taskmgr.h"
#include "khnodemgr.h"
#include "keytext.h"
#include "helpkey.h"

#define XSIZE 65
#define YSIZE 14
#define OVERLAP 2

typedef struct _tasknode {
   struct _tasknode *next;    /* pointer to next entry in linked list */
   char     name[NAMLEN+1];   /* name of associated task */
   char     key[KEYLEN+1];    /* name of user input keyword */
   int      changed;          /* flag indicating contents has changed */
   khnode   current;          /* current keyhelp node */
} _tasknode, *tasknode;

extern int NTASK;
extern int LINES, COLS;                   /* screen dimensions from Curses */

static Window  win=NULL;
static long   kid=0, tid=0, uid=0;
static char   excltsk[NAMLEN+1]="";       /* "exclusive" task name */
static tasknode tasklist=NULL;            /* linked list of task nodes */
static tasknode currtask=NULL;            /* currently active task node */

static void deactivate(void);
static int  getchars(long,int,void*);
static void followuca(long,int,void*);
static void tasknotify(long,TaskStatus,void*);
static void updatewin(void);
static void FindCurrent(char *name, char *key);
static void DeleteTasknode(tasknode node);

/* ========================================================================== */
/*                               HelpKey                                      */
/* -------------------------------------------------------------------------- */
/*  HelpKey initializes the context-sensitive help display for userinp
 *  keywords.
 *  It displays an explanation of the userinp request of which the prompt
 *  is present in the User Command Area.
 */
extern void HelpKey(char *name)
{
   if (kid) {
      if (win->mapped) {
         UnmapWindow(win);
         MapWindow(win,-1,-1);               /* move to top */
      }
      return;                                /* allow only one instance */
   }

   win = CreateWindow(YSIZE,XSIZE);
   win->framed = TRUE;
   if (name) strcpy(excltsk,name);
   updatewin();
   kid = InsertKeyboardProc(getchars,NULL);
   uid = InsertUcaProc(followuca,NULL);
   tid = InsertTaskProc(tasknotify,NULL);
}

/* ========================================================================== */
/*                                StopHelpKey                                 */
/* -------------------------------------------------------------------------- */
/*  StopHelpKey() deactivates the keyword help display if called a taskname
 *  is present. In this way, it is only deactivated when it was activated
 *  by help.c.
 */
extern void StopHelpKey(void)
{
   if (kid && *excltsk) deactivate();
}

/* -------------------------------------------------------------------------- */
/*                               getchars                                     */
/* -------------------------------------------------------------------------- */
/*  Keyboard procedure getchars() handles user interaction.
 */
static int  getchars(long id, int c, void* arg)
{
   if (win->mapped && c==PAGEUP)   c = ESCBIT | '\\';
   if (win->mapped && c==PAGEDOWN) c = ESCBIT | '/';
   if (c&ESCBIT) {
      int cc=c^ESCBIT;
      if (cc==TAB && !*excltsk) {
         deactivate();
         return 0;
      }
      if (win->mapped) {
         switch (cc) {
                                                          /* page forward */
            case '/': {
               khnode khn=currtask->current;
               khn->mapline += (YSIZE-2-OVERLAP);
               if (khn->mapline > (khn->nlines-YSIZE-2))
                  khn->mapline = khn->nlines-YSIZE+3;
               if (khn->mapline < 0) khn->mapline = 0;
               currtask->changed = 1;
               updatewin();
               break;
            }
                                                            /* page back */
            case '\\': {
               khnode khn=currtask->current;
               khn->mapline -= (YSIZE-2-OVERLAP);
               if (khn->mapline < 0) khn->mapline = 0;
               currtask->changed = 1;
               updatewin();
               break;
            }
                                                        /* previous text */
            case '-': {
               khnode khn=currtask->current;
               if (khn->next) {
                  currtask->current = khn->next;
                  DeleteKhnode(khn);
                  currtask->changed = 1;
                  updatewin();
                  break;
               }
            }
                                                          /* first text */
            case '0': {
               khnode khn;
               while (currtask->current->next) {
                  khn = currtask->current;
                  currtask->current = khn->next;
                  DeleteKhnode(khn);
                  currtask->changed = 1;
               }
               updatewin();
               break;
            }

            default: {
               int iref=cc-'0';
                                                       /* get reference */
               if (iref>0 && iref <= currtask->current->nrefs) {
                  char *text=KeyText(currtask->current->ref[iref-1],'\0');
                  if (text) {
                     khnode khn=CreateKhnode(text,XSIZE-2);
                     khn->next = currtask->current;
                     currtask->current = khn;
                     currtask->changed = 1;
                     updatewin();
                  } else {
                     char line[50];
                     sprintf(line," Cannot find reference %s ",
                                    currtask->current->ref[iref-1]);
                     FlashText(win->y,win->x,line,1);
                  }
               } else return c;
            }
         }
         return 0;
      } else return c;
   }
   return c;
}

/* -------------------------------------------------------------------------- */
/*                             followuca                                      */
/* -------------------------------------------------------------------------- */
/*  UCA callback procedure followuca updates the display if necessary.
 */
static void followuca(long id, int reason, void *arg)
{
   if (reason==UCA_PROMPT || reason==UCA_CLEAR) updatewin();
}

/* -------------------------------------------------------------------------- */
/*                             tasknotify                                     */
/* -------------------------------------------------------------------------- */
/*  tasknotify() deletes the structure corresponding with any task that
 *  has exited.
 */
static void tasknotify(long id, TaskStatus task, void *arg)
{
   if (!task->proc) {
      tasknode *i,*next,node;
      for (i=&tasklist; *i; i=next) {
         node = *i;
         next = &node->next;
         if (!strcmp(node->name,task->name)) {
            next = i;
            *i   = node->next;
            if (currtask==node) {
               currtask = NULL;
               updatewin();
            }
            DeleteTasknode(node);
            break;
         }
      }
   }
}

/* -------------------------------------------------------------------------- */
/*                             deactivate                                     */
/* -------------------------------------------------------------------------- */
/*  deactivate() removes the help display and cleans up everything.
 */
static void deactivate(void)
{
   DeleteWindow(win);
   RemoveKeyboardProc(kid); kid = 0;
   RemoveUcaProc(uid); uid = 0;
   RemoveTaskProc(tid); tid = 0;
   while (tasklist) {
      currtask = tasklist;
      tasklist = currtask->next;
      DeleteTasknode(currtask);
   }
   currtask = NULL;
   *excltsk = '\0';
}

/* -------------------------------------------------------------------------- */
/*                             updatewin                                      */
/* -------------------------------------------------------------------------- */
/*  updatewin() changes the contents of the window according to the
 *  information in the UCA. If necessary it maps or unmaps the window.
 */
static void updatewin(void)
{
   char taskname[NAMLEN+1], keyword[KEYLEN+1], topline[80], botline[80];

   strcpy(taskname,NameFromUca());
   if (!*taskname || (*excltsk && strcmp(excltsk,taskname))
                  || !NameToTask(taskname)) {
      UnmapWindow(win);
      return;
   }
   strcpy(keyword,KeyFromUca());
   if (!*keyword) {
      UnmapWindow(win);
      return;
   }
   FindCurrent(taskname,keyword);
   if (currtask->changed) {
      int   i,j,jw,nb;
      khnode khn=currtask->current;
      char  *start=khn->text+(XSIZE-2)*khn->mapline;
      char  *rmvmsg="Pop down: CTRL-U  Off: ESC TAB";

      currtask->changed = 0;
      ClearWindow(win);
      sprintf(topline,"   %s %s", taskname, keyword);
      nb = XSIZE - 2 - strlen(topline) - strlen(rmvmsg);
      if (nb>0) {
         char *start=topline+strlen(topline);
         char *end=start+nb;
         while (start<end) *(start++) = ' ';
         *start = '\0';
         strcat(topline,rmvmsg);
      }
      strcpy(botline,"  Options: ");
      if (currtask->current->mapline) strcat(botline," PageUp ");
      if (( currtask->current->nlines - currtask->current->mapline
            -YSIZE + 3)>0) strcat(botline," PageDown ");
      if (currtask->current->nrefs) {
         strcat(botline," <ESC>1");
         if (currtask->current->nrefs > 1) {
            sprintf(botline,"%s..%d ",botline,currtask->current->nrefs);
         } else {
            strcat(botline," ");
         }
      }
      if (currtask->current->next) strcat(botline," <ESC>-  <ESC>0");
      win->framed = FALSE;
      PutLine(win,0,topline);
      PutLine(win,YSIZE-1,botline);
      win->framed = TRUE;
      jw = 0;
      for (j=khn->mapline; j <= khn->nlines; j++) {
         jw++;
         if (jw > (YSIZE-2)) break;
         for (i=1; i<(XSIZE-1); i++) PutChar(win,jw,i,*(start++));
      }
   }
   if (win->mapped) RefreshWindow(win);
   else MapWindow(win,LINES-NUCA-NTASK-YSIZE,COLS-XSIZE-1);
}

/* -------------------------------------------------------------------------- */
/*                              FindCurrent                                   */
/* -------------------------------------------------------------------------- */
static void FindCurrent(char *name, char *key)
{
   char secname[40];
   char *text;
   tasknode *i, *next, node;
   TaskStatus task;

   if (currtask && !strcmp(name,currtask->name)) {
      if (!strcmp(key,currtask->key)) return; /* name and key already current */
   }

   for (currtask=tasklist; currtask; currtask=currtask->next) {
      if (currtask && !strcmp(name,currtask->name)) {
         if (!strcmp(key,currtask->key)) {
            currtask->changed = 1;
            return;                   /* name and key present and now current */
         } else {
            for (i=&tasklist; *i; i=next) {
               node = *i;
               next = &node->next;
               if (node==currtask) {
                  next = i;
                  *i   = node->next;
                  DeleteTasknode(node);
                  break;
               }
            }                                  /* remove this tasknode */
         }
         currtask = NULL;
         break;
      }
   }
   currtask = New(_tasknode);
   currtask->next = tasklist;
   tasklist = currtask;
   strcpy(currtask->name,name);
   strcpy(currtask->key,key);
   sprintf(secname,"%s.%s",name,key);
   text = KeyText(secname,'*');
   if (!text) text = KeyText(key,'*');
   currtask->current = CreateKhnode(text,XSIZE-2);
   currtask->changed = 1;

   task = NameToTask(name);
   if (task && task->state == WAITINP) {
      UserinpRequest request;
      static char *type, *explain, number[15], *plural;
      char charinfo[80]="";
      char buffer[200];
      static char expllog[]="See also: %%logical input%USERLOG%.\n";
      static char explchr[]="See also: %%character input%USERCHAR%.\n";
      static char expltxt[]="See also: %%text input %USERTEXT%.\n";

      request = (UserinpRequest)&task->request;
      plural = "";
      if (request->number==1) strcpy(number,"One");
      else {
         if (request->level&4) sprintf(number,"Exactly %d",request->number);
         else sprintf(number,"Up to %d",request->number);
         plural = "s";
      }
      switch (request->type) {
         case USERINT:
            type = "integer number";
            explain = "See also: %%number input%USERINT%.\n";
            break;
         case USERLOG:
            type = "logical value";
            explain = expllog;
            break;
         case USERREAL:
            type = "real number";
            explain = "See also: %%number input%USERREAL%.\n";
            break;
         case USERDBLE:
            type = "double precision real number";
            explain = "See also: %%number input%USERDBLE%.\n";
            break;
         case USERCHAR:
         case USERCHARU:
         case USERCHARL:
            type = "character string";
            sprintf(charinfo,"The length of the string%s is %d character%s.",
                    plural,request->width,request->width==1?"":"s");
            explain = (request->number==1)?"": explchr;
            break;
         case USERTEXT:
            type = "character";
            explain = expltxt;
            break;
      }
      AppendKhnode(currtask->current,
      "----------------------------------------------------------------------\n"
      " Detailed request information:\n");
      sprintf(buffer,"\n %s %s%s. %s %s",
                     number,type,plural,explain,charinfo);
      AppendKhnode(currtask->current,buffer);
      if (request->level&(~4))
      AppendKhnode(currtask->current,"\n A default is available.");
      else AppendKhnode(currtask->current,"\n No default is available.");
   }
}

/* -------------------------------------------------------------------------- */
/*                                 DeleteTasknode                             */
/* -------------------------------------------------------------------------- */
static void DeleteTasknode(tasknode tnode)
{
   while (tnode->current) {
      khnode node = tnode->current;
      tnode->current = node->next;
      DeleteKhnode(node);
   }
   Delete(tnode);
}
#<

#>            hermesipc.c
/* hermesipc.c
                              COPYRIGHT (c) 1990
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands


Module:      hermesipc

Purpose:     Collection of routines to manage hermes'  communication with
             servant tasks.

File:        hermesipc.c

Author:      J.P. Terlouw

Description: The following routines are available:

             get_request:  obtain a request from a servant;
             reply_alloc:  allocate reply buffer space;
             reply_free :  release reply buffer;
             reply_put  :  append to the buffer;
             reply_send :  write the result code to the buffer;
             local_msg  :  send a request message on behalf of a servant
                           to Hermes itself.

             These functions are called exclusively by Hermes.

Updates:     22-Oct-90  --  original document
             18-Jan-91  --  revised
*/

#include "stdlib.h"
#include "hermescom.h"
#include "sockio.h"

#if 0
/* functionality replaced by sock_writev */
struct iovec {
   char *iov_base;
   int   iov_len;
};
int writev(int,struct iovec*,int);
#endif

/* sock_read does not work here. Declaration still needed because not
 * all systems may have unistd.h
 */
size_t read(int, void*, size_t);

/*
Function:     get_request

Author:       J.P. Terlouw

Purpose:      Private to hermes. Obtain request from servant.

Use:          request_complete = get_request(status);

               int request_complete : 1 if complete message present,
                                      0 otherwise.
               TaskStatus status    : pointer to task status block.
                                      get_request puts the request in
                                      status->request.

              This function is called exclusively by Hermes.

Updates:      22-Oct-90  --  original document
              18-Jan-91  --  revised
*/

int get_request(TaskStatus task)
{
/*  This routine has special provisions for the case that there is no or
 *  not a complete message. A small weakness is the absence of a check
 *  for incomplete transfers of the int's reqlen and reqpid, which seem
 *  to be very unlikely.
 */

   char *request;

   if (task->reqlen == 0) {
      read(task->proc->fd_master,(char*)&task->reqlen,sizeof(task->reqlen));
      task->reqsize = task->reqlen;
   }
   if (task->reqlen != 0 && task->reqpid == 0)
      read(task->proc->fd_master,(char*)&task->reqpid,sizeof(task->proc->pid));
   if (task->reqlen !=0 && task->reqpid != 0) {
      request = ((char*)&task->request) + task->reqsize - task->reqlen;
      task->reqlen -= read(task->proc->fd_master,request,task->reqlen);
   }
   if (task->reqlen == 0) {
      task->reqpid = 0;
      return 1;
   }
   return 0;
}
/* ========================================================================== */
/*                                reply_alloc                                 */
/* -------------------------------------------------------------------------- */
/*   reply_alloc() allocates buffer space for the request-reply.
 */
extern int reply_alloc(TaskStatus task, int nbytes)
{
   char *newblock=task->reply;
   if (!task->reply_size) {
      newblock = Malloc(nbytes);
      task->reply_size = nbytes;
   } else if (nbytes>task->reply_size) {
      newblock = Realloc(task->reply,nbytes);
      task->reply_size = nbytes;
   }
   if (newblock) {
      task->reply = newblock;
      return 0;
   } else return -1;
}

/* ========================================================================== */
/*                                reply_free                                  */
/* -------------------------------------------------------------------------- */
/*   reply_free() releases the reply buffer
 */
extern void reply_free(TaskStatus task)
{
   if (task->reply_size) {
      free(task->reply);
      task->reply = NULL;
      task->reply_in = 0;
      task->reply_size = 0;
   }
}

/* ========================================================================== */
/*                                reply_send                                  */
/* -------------------------------------------------------------------------- */
/*    Send reply length, error code and reply to servant task.
 *
 *
 *          void reply_send(status, result_code);
 *
 *               TaskStatus status : pointer to the task status block.
 *                                   the reply must have been built before by
 *                                   one or more calls to reply_put.
 *               int result_code   : error code to be sent to the servant task.
 *
 */
void reply_send(TaskStatus status, int result)
{
   int msglen;
   struct sock_iovec iov[3];

   msglen = status->reply_in+sizeof(result);
   iov[0].iov_base = (char*)&msglen;
   iov[0].iov_len  = sizeof(msglen);
   iov[1].iov_base = (char*)&result;
   iov[1].iov_len  = sizeof(result);
   iov[2].iov_base = status->reply;
   iov[2].iov_len  = status->reply_in;

   sock_writev(status->proc->fd_omaster,iov,iov[2].iov_len?3:2);
#if 0 /* +++ only release memory when task has exited (?) */
   reply_free(status);
#else
   status->reply_in = 0;
#endif
   ((Request)&status->request)->code = 0;   /* indicate no request present */
}

/* ========================================================================== */
/*                                reply_put                                   */
/* -------------------------------------------------------------------------- */
/*    Store reply data in task status block buffer.
 *
 * Use:          reply_put( task,  data,  number);
 *
 *             TaskStatus task :   pointer to the task status block.
 *             char      *data :   pointer to the data to be put in the buffer.
 *             int      number :   number of bytes to be transferred.
 */
void reply_put(TaskStatus task, char *data, int number)
{
   if (!reply_alloc(task,number)) {
      for ( ; number>0; number--) {
         task->reply[(task->reply_in)++] = *(data++);
      }
   }
}

/* ========================================================================== */
/*                                local_msg                                   */
/* -------------------------------------------------------------------------- */
/*   Send a message to Hermes' message handler on behalf of servant.
 *
 * Use:          local_msg( task, message);
 *
 *             TaskStatus     task :   pointer to the task status block.
 *             RequestMessage *message :  pointer to the message to be sent.
 *
 *            This function is called exclusively be Hermes.
 *            It is currently used to translate child termination signals to
 *            the general Hermes message protocol and to convert task writes
 *            to stdout and stderr into anyout requests.
 *            In this way the operating system dependent part is separated
 *            from the o/s independent part.
 */
void local_msg(TaskStatus task, RequestMessage *message)
{
   int fd=task->proc->fd_servant;
   struct sock_iovec iov[2];

   iov[0].iov_base = (char*)message;
   iov[0].iov_len  = MSGHED;
   iov[1].iov_base = (char*)message->request;
   iov[1].iov_len  = message->reqlen;
   sock_writev(fd,iov,2);
}
#<

#>            keyboard.c
/* keyboard.c
                           COPYRIGHT (c) 1991, 1995
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes.
Author: J.P. Terlouw.
*/

#include "stddef.h"
#include "keyboard.h"
#include "mainloop.h"
#include "alloc.h"
#include "keysym.h"

typedef struct _Callback {
   int  (*proc)();                  /* procedure pointer */
   void  *arg;
   struct _Callback *next;           /* link to next entry */
} *Callback, _Callback;

static Callback cb_free=NULL, cb_active=NULL;
static int n_active=0;
static int fd=0;                            /* standard input file descriptor */
static long msg_id=0;
static int remresch=0;

static void ReadKB(long, int, void*);

/* ========================================================================== */
/*                             InitKey                                        */
/* -------------------------------------------------------------------------- */
/*  InitKey initializes the keyboard module. It allocates 'nkey' procedure
 *  entries and constructs a linked list from them. If necessary, this
 *  number is increased later automatically. Calling InitKey is optional, but
 *  it must be called before any of the other routines in keyboard.c is called.
 */
extern void InitKey(int nkey)
{
   Callback cb;

   cb_free = NNew(nkey,_Callback);
   cb = cb_free;
   while(--nkey) {
      cb->next = cb+1;       /* construct free list            */
      cb++;
   }
}

/* ========================================================================== */
/*                        InsertKeyboardProc                                  */
/* -------------------------------------------------------------------------- */
/*  InsertKeyboardProc registers a procedure to be called by static routine
 *  ReadKB (which in turn is called by MainLoop) whenever a key on the
 *  keyboard has been pressed. In this call 'arg' as well as the current
 *  character value will be passed as arguments to 'proc'.
 *  The return value of InsertKeyboardProc is a unique identification code
 *  corresponding with the registration, which will also be passed as an
 *  argument to 'proc'.
 *
 *  The prototype of 'proc' is:
 *      int proc(int id, int chr, void *arg);
 *
 *  When a key has been pressed, *all* procedures are called in a last-in,
 *  first-out fashion, until the current character value is zero.
 *  The return value of the current 'proc' is used as argument chr
 *  for the next one.
 */
extern long InsertKeyboardProc(int(*proc)(), void *arg)
{
   Callback cb;

   if (cb_free) {
      cb = cb_free;
      cb_free = cb->next;         /* obtain entry from free list ... */
   } else cb = New(_Callback);    /* ... or create a new entry */

   if (!(n_active++)) msg_id = InsertMessageProc(ReadKB,fd,NULL);
   cb->next = cb_active;           /* move to active list */
   cb_active = cb;
   cb->proc = proc;                /* store procedure pointer */
   cb->arg = arg;
   return (long)cb;
}


/* ========================================================================== */
/*                        RemoveKeyboardProc                                  */
/* -------------------------------------------------------------------------- */
/*  RemoveKeyboardProc removes the registration of the procedure which has
 *  been registered by InsertKeyboardProc().
 */
extern void RemoveKeyboardProc(long id)
{
   ((Callback)id)->proc = NULL;       /* ReadKB will move block to free list */
   remresch++;
   if (!(--n_active)) {               /* empty list? */
      RemoveMessageProc(msg_id);      /* remove message handler */
      msg_id = 0;
   }
}
/* ========================================================================== */
/*                            SuspendKeyboard                                 */
/* -------------------------------------------------------------------------- */
/*  SuspendKeyboard `detaches' the terminal keyboard.
 *  It should be called when a sub-process like an editor or a shell
 *  needs exclusive access to the terminal.
 */
extern void SuspendKeyboard(void)
{
   if (msg_id) {
      RemoveMessageProc(msg_id);
      msg_id = 0;
   }
}

/* ========================================================================== */
/*                            ResumeKeyboard                                  */
/* -------------------------------------------------------------------------- */
/*  ResumeKeyboard `re-attaches' the terminal keyboard.
 */
extern void ResumeKeyboard(void)
{
   if (!msg_id) {
      msg_id = InsertMessageProc(ReadKB,fd,NULL);
   }
}

/* ========================================================================== */
/*                                 Type                                       */
/* -------------------------------------------------------------------------- */
/*  Type() dispatches a type-in character to the keyboard handlers.
 */
extern void Type(int c)
{
   Callback cb, *cbp, *next, last=0;
   int resched=0;

   if      (c==CTRL_H) c = DEL;
   else if (c=='\n')   c = CR; 
   for (cbp=&cb_active; *cbp; cbp=next) {
      cb = *cbp;
      next = &cb->next;
      if (!cb->proc) {
         next = cbp;                 /* invalidated entry: move to free list */
         *cbp = cb->next;
         cb->next = cb_free;
         cb_free = cb;
      } else if (c) {
         c = (*cb->proc)(cb,c,cb->arg);
         if (c==YRESCHED) {
            resched++;              /*  re-schedule acknowledge returned? */
            c = QRESCHED;
            last = cb;
         } else if (c != QRESCHED) {
            resched = 0;            /* handler interpreted reschedule request */
         }
      }
   }
   if (resched>1) {
                          /* move last reschedulable handler to front of list */
      for (cb=cb_active; cb; cb=cb->next) {
         if (cb->next==last) {
            cb->next = last->next;
            last->next = cb_active;
            cb_active = last;
            break;
         }
      }
   }
   if (resched>1 || remresch) {
      remresch = 0;
      c = XRESCHED;
      /* Trigger handlers to perform rescheduling actions.
       * The first handler that understands the rescheduling request
       * returns zero and terminates the loop.
       */
      for (cb=cb_active; cb && c==XRESCHED; cb=cb->next) {
         if (cb->proc) c = (*cb->proc)(cb,c,cb->arg);
      }
   }
}
   
/* -------------------------------------------------------------------------- */
/*                                 ReadKB                                     */
/* -------------------------------------------------------------------------- */
/*  ReadKB reads a character from the terminal keyboard and presents it
 *  to the dispatcher routine.
 */
static void ReadKB(long id, int fd, void *arg)
{
   size_t read(int, void *, size_t);
   
   char ctyped;
   int  c;

   (void)read(fd,&ctyped,1);                    /* read one character */
   c = ctyped;
   Type(c);
}
#<

#>            keytext.c
/* keytext.c
                              COPYRIGHT (c) 1993
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It deals with the userinp keyword help file.

Author: J.P. Terlouw
*/

#include "stddef.h"
#include "stdlib.h"
#include "string.h"
#include "limits.h"
#include "alloc.h"
#include "filecontents.h"
#include "keytext.h"
#define KEYHELPFILE "keyhelp.doc"
#define KEYHELPPATH "gip_doc"

typedef struct _section {
   struct _section *next;   /* pointer to next entry in linked list */
   char            *key;    /* section name */
   char            *text;   /* section contents */
} _section, *section;

static section sections=NULL;
static char *keyfile=NULL;

static void readfile(void);
static int KeyMatch(char*,char*,char);

/* ========================================================================== */
/*                                 KeyText                                    */
/* -------------------------------------------------------------------------- */
/*  KeyText() retrieves the text belonging to a userinp keyword from the file
 *  containing the keyword help. If the wild character is supplied, a wild
 *  match is attempted; otherwise only exact matches are considered.
 *  The function returns a pointer to a block of text. The caller must not
 *  modify this text.
 */
extern char *KeyText(char *key, char wild)
{
   section current;
   char    *besttext=NULL;
   int     goodness, best=0;

   if (!keyfile) readfile();
   for (current=sections; current; current = current->next) {
      goodness=KeyMatch(key,current->key,wild);
      if (goodness>best) {
         best = goodness;
         besttext = current->text;
      }
   }
   return besttext;
}

/* -------------------------------------------------------------------------- */
/*                                 readfile                                   */
/* -------------------------------------------------------------------------- */
/*  readfile() reads the keyword help file and structures its contents for
 *  further use by KeyText()
 */
static void readfile(void)
{
   int  i=0, size;
   char *keystart=NULL, *textstart;
   char *filename;
   section current;

   filename = (char *)Malloc(strlen(getenv(KEYHELPPATH))+strlen(KEYHELPFILE)+2);
   strcpy(filename,getenv(KEYHELPPATH));
   strcat(filename,"/");
   strcat(filename,KEYHELPFILE);
   keyfile = FileContents(filename, &size);
   Free(filename);
   if (keyfile) {
      while (i<size) {
         if (keyfile[i]=='>') {
            keyfile[i] = '\0';          /* terminates any previous text block */
            keystart = &keyfile[++i];
            while (keyfile[i]!='\n') i++;               /* search end-of-line */
            keyfile[i++] = '\0';                             /* terminate key */
            current = New(_section);       /* link new section block in chain */
            current->next = sections;
            sections = current;
            current->key = keystart;
            textstart = &keyfile[i];
         } else if (keystart) {
            for (current=sections; current; current=current->next) {
               if (current->text) break;
               current->text = textstart;
            }                                  /* fill in text block pointers */
            keystart = NULL;
         } else {
            while (keyfile[i]!='\n') i++;               /* search end-of-line */
            i++;
         }
      }
   } else keyfile = "";     /* prevent readfile() to be called more than once */
}

/* -------------------------------------------------------------------------- */
/*                                 KeyMatch                                   */
/* -------------------------------------------------------------------------- */
/*  KeyMatch() matches a test string with the mask string which can contain
 *  wildcards. If an exact match is found, INT_MAX is returned; any other match
 *  causes the number of really matched characters + 1 to be returned.
 *  ('Really matched' means not matched by the wild character.)
 *  Otherwise zero is returned.
 *  (Modified version of wmatch by K.G. Begeman.)
 */
static int KeyMatch(	char	*test ,			/* test string */
			char	*mask ,			/* mask string */
			char	 wild )			/* wildcard character */
{
   int	m = 0, t = 0, r = 0;			/* counters and return value */

   if (!strcmp(test,mask)) return INT_MAX;	/* exact match */
   if (!wild) return 0;                         /* exact match required */

   while (mask[m]) {				/* main loop */
      int	mi = m, ti = t, om = 0;		/* old counters */

      r = 0;					/* reset */
      while (mask[m+om] == wild) om++;		/* skip wildcards */
      if (om && !mask[m+om]) { r = 1; break; }	/* matched, so we quit */
      m += om;					/* update counter */
      if (!m && !t) {				/* nothing processed yet */
         while (mask[m] == test[t]) {		/* compare */
            t += 1; m += 1;			/* update counters */
            if (!mask[m-1]) { r = 1; break; }	/* matched, so we quit */
         }
         if (mask[m] == wild) r = 1;		/* prepare for next run */
      } else {					/* already started */
         int	c = 1;				/* inner loop control */

         while (c) {				/* inner loop */
            while (mask[m] != test[t++]) {	/* find next match */
               if (!test[t]) { c = 0; break; }	/* end of test string */
            }
            if (!c) break;			/* leave inner loop */
            mi = m; m += 1; ti = t;		/* update counters */
            while (mask[m] == test[t]) {	/* compare loop */
               if (!mask[m]) { c = 0; break; }	/* end of mask */
               m += 1; t += 1;			/* update counters */
            }
            if (mask[m] == wild) { c = 0; }	/* set leave flag */
            if (!c) { r = 1; break; }		/* prepare for next run */
            m = mi; t = ti;			/* update counters */
         }
      }
      if (!r) break;				/* leave outer loop */
   }
   if (r) for (r=1; *mask; mask++) if (*mask != wild) r++;
   return r;					/* return to caller */
}
#<

#>            khnodemgr.c
/* khnodemgr.c
                              COPYRIGHT (c) 1993
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It deals with the tHermes keyword help
structures.

Author: J.P. Terlouw.
*/

#define LININCR 20          /* text buffer allocation increment (lines) */

#include "stddef.h"
#include "stdio.h"
#include "string.h"
#include "alloc.h"
#include "parsekeyhelp.h"
#include "khnodemgr.h"

static khnode current;
static int refseen;

static void PutWord(char* word, int length);
static void NewLine(void);
static void PutReference(char *text);
static void CheckBuffer(void);

/* ========================================================================== */
/*                                 CreateKhnode                               */
/* -------------------------------------------------------------------------- */
/*  CreateKhnode() creates a "keyhelp node" containing the specified text
 *  fitting in the specified width.
 */
extern khnode CreateKhnode(char *text, int width)
{
   current = New(_khnode);
   current->ncols = width;
   CheckBuffer();
   refseen = 0;
   ParseKeyHelp(text,PutWord,NewLine,PutWord,PutReference);
   return current;
}

/* ========================================================================== */
/*                              AppendKhnode                                  */
/* -------------------------------------------------------------------------- */
/*  AppendKhnode() appends the specified text to an existing "keyhelp node".
 */
extern void AppendKhnode(khnode id, char *text)
{
   current = id;
   refseen = 0;
   ParseKeyHelp(text,PutWord,NewLine,PutWord,PutReference);
}

/* ========================================================================== */
/*                               DeleteKhnode                                 */
/* -------------------------------------------------------------------------- */
/*  DeleteKhnode() deletes the specified "keyhelp node".
 */
extern void DeleteKhnode(khnode node)
{
   int i;
   for (i=0; i<node->nrefs; i++) Delete(node->ref[i]);
   Delete(node->text);
   Delete(node);
}

/* -------------------------------------------------------------------------- */
/*                                 PutWord                                    */
/* -------------------------------------------------------------------------- */
/*  PutWord() processes a word supplied by  ParseKeyHelp().
 *  It takes care of fitting it on the current line the next line if it
 *  does not fit on the current one.
 */
static void PutWord(char* word, int length)
{
   char *start;

   if (!refseen && *word != ' ') length++;
   if (length > current->ncols) length = current->ncols;
   if (current->col+length > current->ncols)  NewLine();
   start = current->text+current->nlines*current->ncols+current->col;
   if (!refseen && *word != ' ') {
      *(start++) = ' ';
      length--;
      current->col++;
   }
   while (length--) {
      *(start++) = *(word++);
      current->col++;
   }
   refseen = 0;
}

/* -------------------------------------------------------------------------- */
/*                                 NewLine                                    */
/* -------------------------------------------------------------------------- */
/*  NewLine() generates a new line in the text buffer.
 */
static void NewLine(void)
{
   current->nlines++;
   current->col = 0;
   CheckBuffer();
}

/* -------------------------------------------------------------------------- */
/*                              PutReference                                  */
/* -------------------------------------------------------------------------- */
/*  PutReference() stores a reference in the table and outputs the reference
 *  number to the text buffer.
 */
static void PutReference(char *name)
{
   char text[6];

   if (current->nrefs < KH_MAXREFS) {
      current->ref[current->nrefs++] = StrDup(name);
      sprintf(text," [%d]",current->nrefs);
      PutWord(text,strlen(text));
      refseen = 1;
   }
}

/* -------------------------------------------------------------------------- */
/*                               CheckBuffer                                  */
/* -------------------------------------------------------------------------- */
/*  CheckBuffer() allocates memory for the text buffer if necessary.
 */
static void CheckBuffer(void)
{
   char *start, *end;

   if (current->nlines == current->linesall) {
      if (!current->text) {
         current->text = (char*)Malloc(LININCR*current->ncols);
      } else {
         current->text = (char*)Realloc(current->text,
                                (current->linesall+LININCR)*current->ncols);
      }
      start = current->text+current->linesall*current->ncols;
      current->linesall += LININCR;
      end = current->text+current->linesall*current->ncols;
      while (start<end) *(start++) = ' ';
   }
}
#<

#>            mainloop.c
/* mainloop.c
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes.
Author: J.P. Terlouw.
*/
#include "stddef.h"

#include <sys/types.h>
#if     defined(__aix__)
#include <sys/select.h>
#endif
#include <sys/time.h>

#include "mainloop.h"
#include "alloc.h"

#define PERIOD 2361597                /* maximum delay (one lunar month) */

typedef struct _Callback {
   int       fd;                      /* file descriptor */
   int       interval;                /* interval for periodic tasks (s) */
   int       calltime;                /* absolute time for next call (s) */
   void     (*proc)();                /* procedure pointer */
   void      *arg;                    /* argument to procedure */
   struct _Callback *next;            /* link to next entry */
} *Callback, _Callback;


static Callback cb_free=NULL, cb_active=NULL;
static fd_set readfds;
static int maxfds=0;
static int n_active=0;
static int delay=PERIOD;

/* ========================================================================== */
/*                             MainLoop                                       */
/* -------------------------------------------------------------------------- */
/*
 *  MainLoop is the nucleus of tHermes and nHermes from which all events
 *  (with one exception) are dispatched. Whenever necessary it calls the
 *  functions which have been registered before either by InsertMessageProc()
 *  or  by InsertTimerProc().
 *  The exception is the child termination signal handler in taskmgr.c.
 *  This signal handler however transforms the signal into a message which
 *  is sent on behalf of the deceased process and which consequently
 *  will eventually be handled by MainLoop.
 */
extern void MainLoop(void)
{
   Callback cb, *cbp, *next;
   struct timeval timeout, now;

   while (n_active) {
      timeout.tv_sec  = delay;
      timeout.tv_usec = 0;
      while (select(maxfds+1,(void*)&readfds,NULL,NULL,&timeout)==-1) {;}
      gettimeofday(&now,NULL);
      delay = PERIOD;                          /* set next timeout to maximum */
      for (cbp=&cb_active; *cbp; cbp=next) {
         cb = *cbp;
         next = &cb->next;

         if (!cb->proc) {
            next = cbp;               /* invalidated block; move to free list */
            *cbp = cb->next;
            cb->next = cb_free;
            cb_free = cb;

         } else if (cb->fd >= 0) {
            if (FD_ISSET(cb->fd,&readfds)) {
               (*cb->proc)(cb,cb->fd,cb->arg);    /* file descriptor callback */
            }
            if (cb->proc) FD_SET(cb->fd,&readfds);   /* re-set if still valid */

         } else if (cb->calltime <= now.tv_sec) {
               (*cb->proc)(cb,cb->arg);                     /* timer callback */
               if (cb->proc) {
                  cb->calltime = now.tv_sec + cb->interval;
                  if (delay > cb->interval) delay = cb->interval;     /* trim */
               }
         } else if(delay > cb->calltime-now.tv_sec)
               delay = cb->calltime-now.tv_sec;                       /* trim */
      }
   }
}

/* ========================================================================== */
/*                            InsertMessageProc                               */
/* -------------------------------------------------------------------------- */
/*
 *  InsertMessageProc registers a procedure to be called by MainLoop whenever
 *  data can be read from file descriptor 'fd'. In this call, 'arg' will be
 *  passed to 'proc'. The return value is a unique identification code
 *  corresponding with the registration, which will also be passed as an
 *  argument to 'proc'.
 *
 *  The prototype of 'proc' is:
 *      void proc(int id, int fd, void *arg);
 */
extern long InsertMessageProc(void(*proc)(), int fd, void *arg)
{
   Callback cb;

   if (cb_free) {
      cb      = cb_free;
      cb_free = cb->next;        /* obtain entry from free list */
   } else cb = New(_Callback);   /* or allocate a new entry */

   cb->next= cb_active;
   cb_active = cb;
   cb->fd = fd;                  /* store parameters */
   cb->proc = proc;
   cb->arg = arg;
   FD_SET(fd,&readfds);          /* set flag in select() structure */
   if (fd>maxfds) maxfds=fd;     /* update maximum file descriptor value */
   n_active++;
   return (long)cb;              /* use pointer to callback entry as ID */
}

/* ========================================================================== */
/*                             InsertTimerProc                                */
/* -------------------------------------------------------------------------- */
/*  InsertTimerProc registers a procedure to be called by MainLoop whenever
 *  a specified interval in seconds has expired. Until it is removed,
 *  'proc' will be called every 'interval' seconds. In the call 'arg' is passed
 *  as an argument. The return value is a unique identification code
 *  corresponding with the registration, which will also be passed as an
 *  argument to 'proc'.
 *
 *  The prototype of 'proc' is:
 *      void proc(int id, void *arg);
 */

extern long InsertTimerProc(void(*proc)(), int interval, void *arg)
{
   Callback cb;
   struct timeval now;

   if (cb_free) {
      cb      = cb_free;
      cb_free = cb->next;        /* move entry from free list */
   } else cb = New(_Callback);   /* or allocate a new entry */

   cb->next= cb_active;          /* move to active list */
   cb_active = cb;
   cb->fd = -1;                  /* fd==-1 indicates timer block */
   cb->interval = interval;      /* store parameters */
   gettimeofday(&now,NULL);
   cb->calltime = now.tv_sec + interval;
   cb->proc = proc;
   cb->arg = arg;
   if (delay>interval) delay = interval;
   n_active++;
   return (long)cb;              /* use pointer to callback entry as ID */
}

/* ========================================================================== */
/*                            RemoveMessageProc                               */
/* -------------------------------------------------------------------------- */
/*  RemoveMessageProc removes the registration of a procedure which has been
 *  registered by either InsertMessageProc() or by InsertTimerProc().
 */
extern void RemoveMessageProc(long id)
{
   Callback cb=(Callback)id;                 /* convert id to callback pointer*/

   if (cb->fd >= 0) FD_CLR(cb->fd,&readfds); /* clear fd flag                 */
   cb->proc = NULL;                          /* invalidate for MainLoop       */
   n_active--;
}

/* ========================================================================== */
/*                               InitMain                                     */
/* -------------------------------------------------------------------------- */
/*  InitMain initializes the mainloop module. It allocates 'nmes' procedure
 *  entries and constructs a linked list from them. If necessary this number
 *  is increased later automatically. Calling InitMain it optional, but
 *  it must be called before any of the other routines in mainloop.c
 *  is called.
 */
extern void InitMain(int nmes)
{
   Callback cb;

   FD_ZERO(&readfds);
   cb_free = NNew(nmes,_Callback);
   cb = cb_free;
   while(--nmes) {
      cb->next = cb+1;                  /* construct free list */
      cb++;
   }
}
#<

#>            minimatch.c
/* minimatch.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It implements minimal matching for
user input parameter keywords and tasknames based on the .dc1
documentation file in $gip_tsk
Author: J.P. Terlouw
*/

#include "stddef.h"
#include "stdlib.h"
#include "ctype.h"
#include <sys/types.h>
#if defined(__alliant__)
#include <sys/dir.h>
#define dirent direct
#else
#include <dirent.h>
#endif
#include "hermescom.h"
#include "string.h"
#include "bintree.h"
#include "minimatch.h"

typedef struct {
   char *name;
   int  namelen;
   int  ready;
   Node match;
   int  nmatch;
} _vidata, *vidata;


static void Visitor(Node,vidata);
static void ExtractKeys(char*,Tree);
static void ExtractTasks(Tree);

/* ========================================================================== */
/*                             MatchKey                                       */
/* -------------------------------------------------------------------------- */
/*  MatchKey performs minimal matching for a user input parameter keyword
 *  for a given task. It uses the .dc1 documentation file in gip_tsk to
 *  find the keywords. If a unique match is found, it will be returned
 *  via keyout; if there are no matching keywords, or if the match is
 *  ambiguous, keyin is copied to keyout.
 *  The argument 'taskname' must be given in lower case and 'keyin' in
 *  uppercase.
 *  The function returns the number of possible matches.
 */
int MatchKey(char *taskname, char *keyin, char *keyout)
{
   static  Node tasktree=NULL;
   Node tasknode;
   _vidata data={NULL, 0, 0, NULL, 0};
   char filename[80];
   char *text;
   int  filesize;

   tasknode = RetrieveNode(&tasktree,taskname);
   if (!tasknode) {
      tasknode = New(struct _Node);
      strcpy(tasknode->name,taskname);
      (void)InsertNode(&tasktree,tasknode);
      strcpy(filename,getenv("gip_tsk"));
      strcat(filename,"/");
      strcat(filename,taskname);
      strcat(filename,".dc1");
      text = FileContents(filename,&filesize);
      if (text) {
         ExtractKeys(text,(Tree)(&tasknode->data));
         Free(text);
      }
   }
   data.name = keyin;
   data.namelen = strlen(keyin)-1;             /* first compare without '=' */
   TraverseTree((Tree)(&tasknode->data), (TraverseProc)Visitor, &data);
   if (data.nmatch==1) strcpy(keyout,data.match->name);
   else                strcpy(keyout,keyin);
   return data.nmatch;
}

/* ========================================================================== */
/*                             MatchTask                                      */
/* -------------------------------------------------------------------------- */
/*  MatchTask performs minimal matching for a taskname. It uses the directory
 *  gip_exe to find the names (files with the extension ".old" are eliminated).
 *  If a unique match is found, it will be copied to 'nameout'; if there
 *  are no matching tasknames, or if the match is ambiguous, 'namein' is
 *  copied to 'nameout'.
 *  The argument namein must be given in the same case as the executable
 *  filename (usually lowercase).
 *  The function returns the number of possible matches.
 */
extern int MatchTask(char *namein, char *nameout)
{
   static Node tasktree=NULL;
   static int  initialized=0;
   _vidata data={NULL, 0, 0, NULL, 0};

   if (!initialized) {
      ExtractTasks(&tasktree);
      initialized++;
   }
   data.name = namein;
   data.namelen = strlen(namein);
   TraverseTree(&tasktree, (TraverseProc)Visitor, &data);
   if (data.nmatch==1) strcpy(nameout,data.match->name);
   else                strcpy(nameout,namein);
   return data.nmatch;
}

/* -------------------------------------------------------------------------- */
/*                            Visitor                                         */
/* -------------------------------------------------------------------------- */
/*  Visitor is called by TraverseTree for every node in a binary tree.
 *  It stores the last match in the element 'match' of the struct pointed
 *  to by 'data' and maintains a count of matches in the same struct.
 */
static void Visitor(Node node, vidata data)
{
   int k;

   if (!data->ready) {
      k = strncmp(node->name,data->name,data->namelen);
      if (k<0) {
         return;                 /* before any possible match */
      } else if (k==0) {
         data->match = node;
         if (!strcmp(node->name,data->name)) {
            data->nmatch = 1;    /* exact match */
            data->ready = 1;     /* no more tries */
         } else {
            data->nmatch++;
         }
      } else {
         data->ready = 1;        /* after possible matches, no more tries */
         return;
      }
   }
}

/* -------------------------------------------------------------------------- */
/*                           ExtractKeys                                      */
/* -------------------------------------------------------------------------- */
/*  ExtractKeys scans the .dc1 file contents in argument 'df' and stores
 *  any  keywords found in the binary tree 'keytree'.
 */
static void ExtractKeys(char *df, Tree keytree)
{
   enum {START, KEY, SKIP} State=START;
   int count;
   char *keyptr;
   char key[KEYLEN+1];
   Node keynode=NULL;

   while (*df) {
      switch(State) {
         case START: {
            if (*df==' '||*df=='*'||*df=='\t') {
               df++;
               break;
            }
            if (isalpha(*df)) {
               State = KEY;
               keyptr = df;
               count  = 0;
               break;
            }
            State = SKIP;
            break;
         }

         case KEY: {
            if (isalpha(*df)||isdigit(*df)) {
               count++;
               df++;
               if (count==(KEYLEN-1)) {
                  State = SKIP;
               }
               break;
            }
            if (*df=='=') {
               strncpy(key,keyptr,count+1);
               key[count+1] = '\0';
               StringUp(key);
               if (!keynode) keynode = New(struct _Node);
               strcpy(keynode->name,key);
               if (!InsertNode(keytree,keynode)) keynode = NULL;
               State = SKIP;
               break;
            }
            State = SKIP;
            break;
         }

         case SKIP: {
            while (*df && (*df != '\n')) df++;
         }
      }
      if (*df=='\n') {
         df++;
         State = START;
      }
   }
}

/* -------------------------------------------------------------------------- */
/*                           ExtractTasks                                     */
/* -------------------------------------------------------------------------- */
static void ExtractTasks(Tree tasktree)
{
   DIR *directory;
   struct dirent *taskfile;
   Node tasknode=NULL;
   char *ext;
   char *neglist[]={"hermes","thermes","xhermes","nohermes","thelp",NULL};
   int  i;

   directory = opendir(getenv("gip_tsk"));

   while ((taskfile = readdir(directory))) {
      if ((ext=strstr(taskfile->d_name,".dc1"))) {
         if (!tasknode) tasknode = New(struct _Node);
         strcpy(tasknode->name,taskfile->d_name);
         tasknode->name[ext-taskfile->d_name] = '\0';
         if (!InsertNode(tasktree,tasknode)) tasknode = NULL;
      }
   }
   (void)closedir(directory);
   for (i=0; neglist[i]; i++) {
      tasknode = UnlinkNode(tasktree,neglist[i]);
      if (tasknode) Free(tasknode);
   }
}
#<

#>            monopoly.c
/* monopoly.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It implements a facility which allows
subprocesses to be started which temporarily monopolize the tHermes'
control terminal, while allowing all other activity to be continued
'behind the scenes'.

Author: J.P. Terlouw.
*/

#include <unistd.h>

#include "stddef.h"
#include "stdlib.h"
#include "stdarg.h"
#include "alloc.h"
#include "mainloop.h"
#include "keyboard.h"
#include "windows.h"
#include "terminal.h"
#include "thermescom.h"
#include "taskmgr.h"
#include "monopoly.h"

static int active=0;
static int  proc;
static MonExitProc callback;

static void getmessage(long id, int fd, void *arg);

/* ========================================================================== */
/*                               Monopoly                                     */
/* -------------------------------------------------------------------------- */
extern void Monopoly(char *path, char *arg, MonExitProc exproc)
{
   int     ch[2];  /* message channel to taskmgr */
   extern char **environ;

   if (active) return;                          /* enforce only one instance */
   active = 1;
   callback = exproc;
   SuspendKeyboard();
   SuspendScreen();
   Terminal(PRVTERM);
   pipe(ch);
   ExitChan(ch[1]);
   (void)InsertMessageProc(getmessage,ch[0],NULL);
   proc = fork();
   if (!proc) {
      /* in child process */
      int i;
      for (i=getdtablesize(); i>2; --i) close(i);
      if (arg) exit(execle(path, path, arg, NULL, environ));
      else     exit(execle(path, path,      NULL, environ));
   } else if (proc==-1) {
      /* fork failed */
      int pidstat[2];

      pidstat[0] = proc;
      pidstat[1] = proc;
      write(ch[1],pidstat,sizeof(pidstat));
   }
}
/* -------------------------------------------------------------------------- */
/*                              getmessage                                    */
/* -------------------------------------------------------------------------- */
static void getmessage(long id, int fd, void *arg)
{
   int pidstat[2];

   (void)read(fd,pidstat,sizeof(pidstat));
   if (pidstat[0]==proc) {
      RemoveMessageProc(id);
      ExitChan(0);   /* causes other end of pipe to be closed in taskmgr */
      close(fd);     /* close own end */
      Terminal(HERTERM);
      ResumeScreen();
      ResumeKeyboard();
      if (callback) (*callback)(pidstat[1]);
      active = 0;
   }
}
#<

#>            ngetarguments.c
/* ngetarguments.c
                              COPYRIGHT (c) 1991
                     Laboratory for Space Research Groningen
           University of Groningen - 9700 AV Groningen, The Netherlands

This module is private to NHermes.
Author: M.A.M. Ruiter (stagair).
*/
#include "stdio.h"

#include "nhermescom.h"


#define LOGEXT  ".LOG"                /* extention of log file                */

static void UsageMessage(void);
static int  LogExt(char*);

/* Argument passing to a structure */
extern Arguments *GetArguments(char **_argv)
{
   #define FLAGID  '-'                /* flag indicator                       */
   #define LOGFLE  'l'                /* log file flag                        */
   #define COAFLE  's'                /* COA (pseudo screen-) file flag       */
   #define PTIME   't'                /* max. processing time flag of NHermes */

   Arguments *arg;
   char **usercommands= NULL;
   int logfileflag = 0, ptimeflag = 0, errflag = 0, coafileflag = 0;

   arg =  (Arguments *)Malloc(sizeof(Arguments));      /* initialize         */
   arg->usercommands = NULL;
   arg->ptime       = 0;
   arg->logfile     = NULL;
   arg->coafile     = NULL;

   *_argv++;
   while (*_argv) {
      switch(*(*_argv)) {
         case FLAGID : switch(*(*_argv + 1)) {
				       /* if   logfileflag already used or no */
				       /*      logfilename given              */
				       /* then message of usage and exit.     */
				       /* else pass log file name to arg      */
				       /*      structure                      */
                         case LOGFLE : if (logfileflag || !strlen((*_argv + 2)))
                                         errflag++;
                                       else {
                                         logfileflag++;
    	                                 /* log file name ends with '.LOG' ? */
                                         if (!LogExt((*_argv + 2))) {
					   /* make room voor LOGEXT */
                                           arg->logfile = (char *)Malloc \
					   ((strlen(LOGEXT) + strlen \
					   ((*_argv + 2))) * sizeof(char));
                                           strcpy(arg->logfile,(*_argv + 2));
                                           strcat(arg->logfile, LOGEXT);
                                         }
                                         else arg->logfile = (*_argv + 2);
                                       }
                                       break;
				       /* if   ptimeflag already used or no */
				       /*      time specification given     */
				       /* then message of usage and exit.   */
				       /* else pass time out to arg struct  */
                         case COAFLE : if (coafileflag || !strlen((*_argv + 2)))
                                         errflag++;
                                       else {
                                         coafileflag++;
    	                                 /* file name ends with '.LOG' ? */
                                         if (!LogExt((*_argv + 2))) {
					   /* make room voor LOGEXT */
                                           arg->coafile = (char *)Malloc \
					   ((strlen(LOGEXT) + strlen \
					   ((*_argv + 2))) * sizeof(char));
                                           strcpy(arg->coafile,(*_argv + 2));
                                           strcat(arg->coafile, LOGEXT);
                                         }
                                         else arg->logfile = (*_argv + 2);
                                       }
                                       break;
				       /* if   ptimeflag already used or no */
				       /*      time specification given     */
				       /* then message of usage and exit.   */
				       /* else pass time out to arg struct  */
                         case PTIME  : if (ptimeflag || !strlen((*_argv + 2)))
                                         errflag++;
                                       else {
                                         ptimeflag++;
                                         arg->ptime = atoi(*_argv + 2);
                                       }
                                       break;
                         default     : errflag++;            /* no such flag */
                                       break;
                       }
		       *_argv++;
                       break;
         default     : if (!arg->usercommands) {
                          arg->usercommands= _argv;
                          usercommands = arg->usercommands;
                       }
                       else *usercommands = *_argv;
		       *_argv++;
                       *usercommands++;
                       break;
      }
      if (errflag) UsageMessage();                     /* print error message */
   }
   if (!arg->usercommands) UsageMessage();
   *usercommands = NULL;
   return arg;
}

static void UsageMessage(void)
{
   fprintf(stderr,
	   "\nusage: nhermes [-l<log file name>] [-t<minutes>] [-s<screen file name>] {<user command>}\n\n");
   exit (1);
}

static int LogExt(char *logfilename)
{
   if (strcmp((logfilename + strlen(logfilename) - strlen(LOGEXT)), LOGEXT) != 0)
      return 0;
   return 1;
}
#<

#>            nhermes.c
/* nhermes.c
			      COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands


Authors: J.P. Terlouw,
         M.A.M. Ruiter (stagiair).
*/

#include "stdlib.h"
#include "stdio.h"
#include "ctype.h"
#include "string.h"

#include <sys/time.h>
#include <sys/file.h>
#include <unistd.h>
#include <signal.h>

#include "nhermescom.h"

#define DNTASK    8                /* maximum number of tasks */
#define IPROC    16                /* maximum number of processes */
#define NSUB     40                /* maximum number of keyword substitutions */
#define ETIMERS  10                /* maximum number of timer procedures */
#define NMESH    (2*IPROC+ETIMERS+1) /* maximum number of message handlers */

int NTASK=DNTASK;
/*
 * =================  Static routine declarations ============================
 */

extern int  open_log(int);
extern TaskStatus NameToTask(char *name);

static void UnspecifiedKeywordDispatch(TaskStatus);
static void MultipleUserCommands(void);
static void AbortTask(TaskStatus);
static TaskStatus LastActiveTask(TaskStatus);
                                                  /* Timer procedures */
static void StartUserCommand(long,char*);
static void TimeOut(long);
static void Finish(long);
                                                  /* Message handlers */
static void TaskRequest(long,int,TaskStatus);
                                                  /* Misc. services   */
static void CleanUp(TaskStatus);
static void isproc(TaskStatus);

static char *coaname=NULL;

/*
 * ================= Static variables global to this module ==================
 */
static struct _TaskStatus template;      /* task status block template        */
static TaskStatus tasklist;              /* task status block list head       */
static Arguments *arg;                   /* arguments given to NHermes        */
static long timeoutid;                   /* time out id                       */
static int laststate;                    /* last task state before finish     */
static int tasksactive=0;                /* number of currently active tasks  */
static int taskspausing=0;               /* number of currently pausing tasks */
static long cmdid;                       /* command id from parser            */

TaskStatus Template=&template;

/* ========================================================================== */
/*                           M A I N   P R O G R A M                          */
/* -------------------------------------------------------------------------- */
int main(int argc, char **argv)
{
  int    i;

  TaskStatus task;
  struct _Environment *env;         /* implementation dependent environment  */

  arg = GetArguments(argv);
  if (arg->logfile)
    chlogname(arg->logfile);
  if (open_log(0)) {
    fprintf(stderr,"Waiting until other session finished...");
    (void)open_log(1);
    fprintf(stderr," running.\n");
  }
  if (arg->coafile) {
    coaname = strdup(arg->coafile);
  }
  if (arg->usercommands) {
    strcpy(template.name,"(template)");         /* template default contents  */
    template.isproc = isproc;
    template.errlev = 4;
    template.meslev = 1;
    template.devmask= DEVMASK;
    template.output_mode = NORMAL;
    template.status_mask = S_TERM;
    getcwd(template.workdir, DIRLEN);

    ReadContext(&template);
    if (NTASK<DNTASK) NTASK=DNTASK;             /* default should be minumum  */
    env = Malloc(NTASK*sizeof(struct _Environment));
    tasklist = inittask(NTASK,IPROC,NSUB);      /* initialize task manager    */
    i = 0;
    for (task=tasklist; task; task=task->next) {
      env[i].task = task;                       /* back pointer to t.s.b.     */
      task->isdata = &env[i];                   /* pointer from t.s.b.        */
      i++;
    }

    InitMain(NMESH);                            /* initialize mainloop module */
    if (arg->ptime != 0)                        /* set time out time to ...   */
      timeoutid= InsertTimerProc(TimeOut, arg->ptime*60, NULL);/* ... seconds */
                                                   
    (void)InsertTimerProc(StartUserCommand, 0, *arg->usercommands);
						/* start user command         */
    MainLoop();                                 /* ACTION!                    */
    close_par();                                /* catch unexplained return   */
  }
}

/* ========================================================================== */
/*                                isproc                                      */
/* -------------------------------------------------------------------------- */
/*
 *  This procedure is called at various occasions by the implementation
 *  independent routines, e.g. do_request, taskmgr. via a pointer to this
 *  procedure in the task status block.
 */
static void isproc(TaskStatus task)
{
   laststate = task->state;

   switch (task->state) {
      case IDLE    :    break;

      case WAITRUN :    task->proc->id_master =
                        InsertMessageProc(TaskRequest,
                                          task->proc->fd_master,task);
                        EnableTskOut(task);
                        put_psa(task,"WAITING TO BE RUN",S_STATUS);
                        /* WARNING: "task" changes meaning here ! */
                        if (!task->proc->next) tasksactive++;
                        break;

      case RUNNING :    put_psa(task,"RUNNING",S_STATUS); break;

      case WAITINP :    if (task->proc->evdr_parent ||
                           (task->caller && task->caller->proc->fd_notify)) {
                           char message[SW];  /* why this declaration ? */
                           UserinpRequest req=(UserinpRequest)&task->request;
                           if (req->level == 1) req->level = 2;
                           if (!satisfy(task, req)) {
                              task->state = NODEFAULT;
                              aborttask(task);
                           }
                        } else if (task->xcaller) {
                           NotifyCaller(task, 1);
                        } else {
                           UnspecifiedKeywordDispatch(task);
                        }

      case PAUSING :    taskspausing++; break;

      case STARTFAIL:   if (!task->proc->next) {
                           put_psa(task,"COULD NOT BE STARTED",S_TERM);
                        }
                        CleanUp(task);
			break;

      case FINISHED:    if (!task->proc->next) {
                           put_psa(task," +++ FINISHED +++",S_TERM);
                        }
                        CleanUp(task); break;

      case CRASHED :    if (!task->proc->next) {
                           put_psa(task," CRASHED",S_TERM);
                        }
                        CleanUp(task);
			break;

      case FATAL   :    CleanUp(task);
			break;

      case ABORTED :    if (!task->proc->next) {
                           put_psa(task," HERMES ABORT",S_TERM);
                        }
                        CleanUp(task); break;

      case NODEFAULT :  CleanUp(task); break;

      default      :    break;
   }
}

/* ========================================================================== */
/*                         UnspecifiedKeywordDispatch                         */
/* -------------------------------------------------------------------------- */
/*
 *  Handle the keyword as if it was hidden. Put a error message in the log
 *  file if there is no default found and terminate all servant tasks.
 *  Put a message of success in the log file if a default is found.
 */
static void UnspecifiedKeywordDispatch(TaskStatus task)
{
   char message[80];
   UserinpRequest req;

   req = (UserinpRequest)&task->request;
   if (req->level == 1)
     req->level = 2;
   if (satisfy(task, req)) {
     sprintf(message, "Default used for %s,%s",\
             task->name, req->key);
     put_log(task, message);
   }
   else {
     sprintf(message, "No default found for %s,%s", \
             task->name, req->key);
     put_log(task, message);
     AbortTask(LastActiveTask(tasklist));
   }
}


/* ========================================================================== */
/*                               TaskRequest                                  */
/* -------------------------------------------------------------------------- */
/*
 *  Message handler to be called by mainloop() at the occasion of
 *  a servant task request.
 */
static void TaskRequest(long id, int fd, TaskStatus task)
{
   if (get_request(task)) do_request(task);
}

/* ========================================================================== */
/*                                  CleanUp                                   */
/* -------------------------------------------------------------------------- */
/*
 *  Process termination clean-up routine.
 *  Called whenever a task terminates.
 */
static void CleanUp(TaskStatus task)
{
   if (!task->proc->next) {
   /* last process of task ? */
/*
 *   Trim the parameters to a minimum size. This is especially inportant
 *   for tasks with recall file parameters.
 */
      task->keep = 1;
      init_par(task);
/*
 *   XEQ-related stuff
 */
/* +++ to be moved to rundown() in taskmgr.c */
      if (task->callee) task->callee->caller = NULL;
      if (task->caller) {
	 int result;
	 switch (task->state) {
	    case FINISHED:  result =  1; break;
	    case STARTFAIL: result = -2; break;
	    case FATAL:     result = -3; break;
	    case CRASHED:   result = -4; break;
	    case ABORTED:   result = -5; break;
	    case NODEFAULT: result = -3; break; /* perhaps own result code? */
	    default:        result = -999; break;
         }
	 reply_send(task->caller,result);
	 (*task->caller->isproc)(task->caller);
      }
      tasksactive--;
   }
/*
 *   Remove file descriptor-related callbacks
 */
   RemoveMessageProc(task->proc->id_master);
   DisableTskOut(task);
   
   if (tasksactive == 0)
     MultipleUserCommands();
}


/* ========================================================================== */
/*                            MultipleUserCommands                            */
/* -------------------------------------------------------------------------- */
/*
 *  start next user command if available and reset time out
 */
static void MultipleUserCommands(void)
{
  if (arg->usercommands[1] == NULL)
    Finish(0);
  else {
    if (arg->ptime != 0) {
      RemoveMessageProc(timeoutid);
      timeoutid = InsertTimerProc(TimeOut, arg->ptime*60, NULL);
    }
    *arg->usercommands++;
    (void)InsertTimerProc(StartUserCommand, 1, *arg->usercommands);
  }
}


/* ========================================================================== */
/*                             StartUserCommand                               */
/* -------------------------------------------------------------------------- */
/*
 *  Timer procedure to start user command.
 */
static void StartUserCommand(long id, char *usercommand)
{
   (void)Exec(&template, usercommand);
   if (id) RemoveMessageProc(id);
}

/* ========================================================================== */
/*                                Exec                                        */
/* -------------------------------------------------------------------------- */
/*
 *  Process a command typed by the user or requested from a task using XEQ.
 */
extern TaskStatus Exec(TaskStatus caller, char *cmdlin)
{
   TaskStatus task=NULL ;
   char *taskname, *execname;
   char *key, *value;
   int  status;
   char message[80];
   char *c;
   int  charleft;

   status = ParseCmd(cmdlin);
   if (status) {
      if (status==NOTSKNAME) {
         put_log(NULL, cmdlin);
         err_uca(NULL, "^-- command without taskname");
         status = EvalExpr(cmdlin);         /* treat as assignment statement */
         if (status && caller==&template) {
            err_uca(NULL,dcderrstr(status));
            return NULL;
         }
         if (caller==&template) {
            strcpy(message,"<USER> ");
         } else {
            strcpy(message,"<");
            strcat(message,caller->name); strcat(message,"> ");
         }
         /* --- ... duplicated code --- */
         charleft = strlen(cmdlin)-(80-strlen(message)-1);
         c = cmdlin + (80-strlen(message)-1);
         strncat(message,cmdlin,80-strlen(message)-1);
         put_log(caller,message);
         while (charleft>0) {
            strcpy(message,"       ");
            strncat(message,c,(80-8));
            charleft -= (80-8);
            c += (80-8);
            put_log(caller,message);
         }
         /* --------------------------- */
         if (status) return NULL;
         return (TaskStatus)-1;                    /* *negative* success! */
      }
      if (caller==&template) {
         put_log(NULL, cmdlin);
         err_uca(NULL, CmdErrString(0));
      }
      return NULL;
   }

   cmdid = CmdGetInfo();
   taskname = CmdTaskName(cmdid);
   execname = CmdExecName(cmdid);
   task = NameToTask(taskname);
   if (!task) {
      for (task=tasklist; task; task=task->next) {
         if (!task->proc) {
            int i;
            TaskStatus next = task->next;            /* save fixed components */
            void *isdata = task->isdata;
            TaskStatus *xcallee=task->xcallee;

            *task        = *caller;                  /* inherit from caller   */
            task->next   = next;                     /* restore f. components */
            task->isdata = isdata;
            task->xcallee= xcallee;

            task->proc   = NULL;                     /* clear process pointer */
            task->state  = IDLE;                     /* clear process state   */
            task->keytree= NULL;                     /* clear userinp parms   */
            task->callee = NULL;                     /* clear callee          */
            task->xcaller= NULL;                     /* clear xcaller         */
            for (i=0; i<NTASK-1; i++) {
               task->xcallee[i] = NULL;              /* clear callees         */
            }
            task->reply  = NULL;                     /* reply buffer          */
            task->reply_in   = 0;                    /*  ,,     ,,   pointer  */
            task->reply_size = 0;                    /*  ,,     ,,   size     */
            task->stdo_in    = 0;                    /* stdout buffer pointer */
            task->keyfile = NULL;                    /* allpar file pointer   */
            break;
         }
      }
      if (!task) {
         if (caller==&template) err_uca(task,"NO FREE TASK SLOT");
         CmdDelete(cmdid);
         return NULL;
      }
      execname = FindExec(execname);
      if (!execname) {
         if (caller==&template) err_uca(task,"NO SUCH TASK AVAILABLE");
         CmdDelete(cmdid);
         return NULL;
      }
   } else {
      if (!CmdNkeys(cmdid)) {   /* no keywords: must be attempt to start task */
         if (caller==&template) err_uca(task,"ALREADY ACTIVE");
         CmdDelete(cmdid);
         return NULL;
      }
   }
   if (!task->proc) {
      task->keep  = CmdMacro(cmdid);                            /* macroflag */
      strcpy(task->name,taskname);
      init_par(task);
      if (!CmdMacro(cmdid)) {
         if (rdefaults(task) && caller==&template) {
            err_uca(task,"ERROR IN DEFAULT FILE");
            CmdDelete(cmdid);
	    return NULL;
         }
      }
   }
   while (1) {
      CmdParam(cmdid,&key,&value);
      if (!key) break;
      insert_par(task, key, value, 0);
   }
   if (caller==&template) {
      strcpy(message,"<USER> ");
   } else {
      strcpy(message,"<"); strcat(message,caller->name); strcat(message,"> ");
   }
   charleft = strlen(cmdlin)-(80-strlen(message)-1);
   c = cmdlin + (80-strlen(message)-1);
   strncat(message,cmdlin,80-strlen(message)-1);
   put_log(caller,message);
   while (charleft>0) {
      strcpy(message,"       ");
      strncat(message,c,(80-8));
      charleft -= (80-8);
      c += (80-8);
      put_log(caller,message);
   }
   if (!task->proc) {
      if (!strcmp(execname+strlen(execname)-4,".col")) {      /* cola script? */
	 execname[strlen(execname)-4] = 0;                    /* strip ".col" */
         insert_par(task,"NAME=",execname, 0); /* name becomes input for cola */
         execname = "cola";                    /* and cola will be started    */
      }
      if (!starttask(task,execname)) kill(getpid(),SIGQUIT);
                                  /* start task; in case of failure dump core */
      strcpy(task->name,taskname);   /* starttask may have changed it  */
      if (caller!=&template) {
         caller->callee = task;
         task->caller = caller;
         strcpy(message,"WAITING FOR "); strcat(message,task->name);
         put_psa(caller,message,S_STATUS);
      }
      if (CmdMacro(cmdid)) {
         if (!write_par(task->name,"keys.tmp")) {
            char *keys;
            int size;
            keys = FileContents("keys.tmp",&size);
            if (keys) {
               int i, col=0;
   
               put_log(caller," ");
               put_coa(caller," ");
               sprintf(message,"Current parameters of %s:",task->name);
               put_log(caller,message);
               put_coa(caller,message);
               for (i=0; i<size; i++) {
                  if (keys[i]=='\n') {
                     message[col++] = ' ';
                  } else {
                     message[col++] = keys[i];
                  }
                  if (col==1 && message[col-1]==' ') col = 0;
                  if (col==80 || (col>70 && message[col-1]==' ')) {
                     message[col] = '\0';
                     put_log(caller,message);
                     put_coa(caller,message);
                     col = 0;
                  }
               }
               if (col) {
                  message[col] = '\0';
                  put_log(caller,message);
                  put_coa(caller,message);
                  put_log(caller," ");
                  put_coa(caller," ");
               }
               free(keys);
            }
            remove("keys.tmp");
         }
      }
   } else if (task->state==WAITINP) {
        (void)satisfy(task,(UserinpRequest)&task->request);
   }
   CmdDelete(cmdid);
   return task;
}

/* ========================================================================== */
/*                                  Finish                                    */
/* -------------------------------------------------------------------------- */
/*
 *  Timer procedure which terminates the session as soon as there are no
 *  active tasks left.
 */
static void Finish(long id)
{
   TaskStatus task;

   for (task=tasklist; task; task=task->next) {
      if (task->proc) break;
   }
   if (!task) {
      close_par();
      FinisNotify();
      if ((arg->ptime!=0 && !timeoutid) ||                       /* time out  */
	  laststate == STARTFAIL || laststate == CRASHED ||      /*  or bad   */
	  laststate == FATAL || laststate == ABORTED)            /* execution */
        exit(1);
      else exit(0);
   }
   if (!id)
      InsertTimerProc(Finish,1,NULL);
}


/* ========================================================================== */
/*                              NameToTask                                    */
/* -------------------------------------------------------------------------- */
/*
 *   Translate a task name to a task status block pointer.
 */
extern TaskStatus NameToTask(char *name)
{
   TaskStatus task;

   if (!name || !*name) return &template;

   for (task=tasklist; task; task=task->next) {
      if (task->proc && !strcmp(name,task->name)) return task;
   }

   return NULL;
}

/* ========================================================================== */
/*                                  put_psa                                   */
/* -------------------------------------------------------------------------- */
/*
 *  Put a message in a Task Status Area
 */
extern void put_psa(TaskStatus task, char *message, int mkind)
{
   char logline[LINLEN];

   strcpy(task->stamessage, message);
   NotifyCaller(task, 2);                      /* notify xeqcont() caller */
   if (task->status_mask & mkind) {
      strcpy(logline,"<STATUS>  ");
      strcat(logline,task->name);
      strcat(logline,"  ");
      strcat(logline,message);
      put_log(task,logline);
   }
}


/* ========================================================================== */
/*                                  put_coa                                   */
/* -------------------------------------------------------------------------- */
/*
 *  Put a message in the Common Output Area
 */
extern void put_coa(TaskStatus task, char *line)
{
   static int fd=-1;
    
   if (fd<0) {
      if (coaname) fd = open(coaname, O_CREAT|O_RDWR|O_APPEND, 0666 );     
      if (fd<0) fd = 0; /* failed, don't try again */
   }

   if (fd) {
      (void)write(fd, line, strlen(line));
      (void)write(fd, "\n", 1);
   }
}


/* ========================================================================== */
/*                                err_uca                                     */
/* -------------------------------------------------------------------------- */
/*
 *  Put an error message in the log file and exit
 *  NOTE: The user command has caused an error by trying to execute it
 */
extern void err_uca(TaskStatus task, char *message)
{
   if (task && message && *message) {
      if (task->ucamessage) Delete(task->ucamessage);
      task->ucamessage = StrDup(message);
   }
   if (message && *message) {
      if (task && task->proc &&
          (task->proc->fd_notify || task->proc->evdr_parent))
         return;                                             /* event-driven */
      if (cmdid) put_log(task, CmdTaskName(cmdid));
      put_log(task, message);
      close_par();
      if (!task || !task->xcaller) exit(1);
   }
}


/* ========================================================================== */
/*                                put_uca                                     */
/* -------------------------------------------------------------------------- */
/*
 *  Put a message (prompt) in the User Command Area
 */
extern void put_uca(TaskStatus task, char *text)
{
}

/* ========================================================================== */
/*                                TimeOut                                     */
/* -------------------------------------------------------------------------- */
/*
 *  Timer Procedure to put a time out message in the log file and call for
 *  AbortTask().
 */
static void TimeOut(long id)
{
   char message[80];
   TaskStatus ltask;

   if (id) {
     RemoveMessageProc(id);
     timeoutid = 0;
   }
   ltask = LastActiveTask(tasklist);
   if (ltask) {
     sprintf(message, "The maximum time of %d minute(s) that you may use", \
	     arg->ptime);
     sprintf(message, "%s NHermes per script is", message);
     put_log(&template, message);
     sprintf(message, "exceeded...");
     put_log(&template, message);
     AbortTask(ltask);
  }
}


/* ========================================================================== */
/*                               AbortTask                                   */
/* -------------------------------------------------------------------------- */
/*
 *  Put a message in the log file and abort the last called servant task.
 *  NOTE: In case that the servant task is running under COLA:
 *          COLA will terminate itself, because of the abortion of this
 *          task which he has called by xeq.
 *        NHermes continues with the next user command if there is any.
 */
static void AbortTask(TaskStatus task)
{
   char message[80];

   if (arg->usercommands[1]) {
     sprintf(message, "NHermes will now terminate all servant tasks and");
     sprintf(message, "%s continue with the next script.", message);
   }
   else
     sprintf(message, \
             "All servant tasks and NHermes itself will now be terminated...");
   put_log(&template, message);

   aborttask(task);
}
/* ========================================================================== */
/*                                LastActiveTask                              */
/* -------------------------------------------------------------------------- */
/*
 *  Search for the last active servant task
 */
static TaskStatus LastActiveTask(TaskStatus task)
{
  while (task->callee && task->callee->proc) {
    task = task->callee;
  }
  if (task->proc)
    return task;
  return NULL;
}
/* ========================================================================== */
/*                              SetPrinter                                    */
/* -------------------------------------------------------------------------- */
/*
 *  Stub for ReadContext. Function not supported in nHermes.
 */
extern int SetPrinter(int pno)
{
   return 0;
}
/* ========================================================================== */
/*                              SelectTek                                     */
/* -------------------------------------------------------------------------- */
/*
 *  Stub for ReadContext. Function not supported in nHermes.
 */
extern int SelectTek(char *name)
{
   return 0;
}
/* ========================================================================== */
/*                              AidUca                                        */
/* -------------------------------------------------------------------------- */
/*
 *  Stub for ReadContext. Function not supported in nHermes.
 */
extern void AidUca(void)
{
}

/* Stub (function not supported in nHermes) */
extern int EditFile(char* filename)
{
   return EDT_NOEDITOR;
}
/* Stub (function not supported in nHermes) */
extern long InsertEditProc(EditProc proc,void *arg)
{
   return 0;
}
/* Stub (function not supported in nHermes) */
extern void RemoveEditProc(long id)
{
}

/* Stub (function not supported in nHermes) */
extern void SetKeyHelp(long id)
{
}

/* Stub (function not supported in nHermes) */
extern void UcaType(char c)
{
}
#<

#>            notifier.c
/* notifier.c
                              COPYRIGHT (c) 1997
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands
*/

#include "stddef.h"
#if defined(__sysv__) | defined(__APPLE__)
#include <unistd.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include "string.h"
#include "stdio.h"
#include "hermescom.h"
#include "notifier.h"

static int initialized=0;
static struct sockaddr_un name;
static int s;

/* -------------------------------------------------------------------------- */
/*                                 InitNotify                                 */
/* -------------------------------------------------------------------------- */
/*  InitNotify() initializes the notifier modume.
 */
static int InitNotify(void)
{
   if ((s = socket(AF_UNIX, SOCK_STREAM, 0))<0) return 1;  /* failure */
   name.sun_family = AF_UNIX;
   sprintf(name.sun_path,"/tmp/notify%d",getpid());
   if (bind(s,(struct sockaddr *)&name,sizeof(name))) return 1; /* failure */
   if (listen(s,5)) return 1;
   return 1;
}

/* ========================================================================== */
/*                                 NotifyName                                 */
/* -------------------------------------------------------------------------- */
/*  NotifyName() returns a pointer the name of Unix socket to which tasks
 *  can make a connection.
 */
extern char *NotifyName(void)
{
   if (!initialized) initialized = InitNotify();
   return name.sun_path;
}

/* ========================================================================== */
/*                                 StartNotify                                */
/* -------------------------------------------------------------------------- */
/*  StartNotify() accepts a connection from a task (process actually)
 *  and stores the associated file descriptor in the process status block.
 */
extern int StartNotify(TaskStatus curstat)
{
   int addr, size;
   int cs;
    
   if (!initialized) initialized = InitNotify();
   size = sizeof(addr);
   cs = accept(s, (struct sockaddr*)&addr, &size);
   (void)fcntl(cs, F_SETFL, O_NONBLOCK);
   if (cs>0) {
      curstat->proc->fd_notify = cs;
      return 0;
   } else return -1;
}

/* ========================================================================== */
/*                                 StopNotify                                 */
/* -------------------------------------------------------------------------- */
/*  StopNotify() terminates notification.
 */
extern void StopNotify(TaskStatus curstat)
{
   if (!initialized) initialized = InitNotify();
   if (curstat && curstat->proc && curstat->proc->fd_notify) {
      (void)close(curstat->proc->fd_notify);
      curstat->proc->fd_notify = 0;
   }
}

/* ========================================================================== */
/*                                 NotifyTask                                 */
/* -------------------------------------------------------------------------- */
/*  NotifyTask() sends an event notification to the task if notifying
 *  is on.
 */
extern void NotifyTask(TaskStatus curstat, char *event, int nbytes)
{
   if (!initialized) initialized = InitNotify();
   if (curstat && curstat->proc && curstat->proc->fd_notify) {
      (void)write(curstat->proc->fd_notify, event, nbytes);
   }
}

/* ========================================================================== */
/*                                 FinisNotify                                */
/* -------------------------------------------------------------------------- */
/*  FinisNotify() closes and removes the notification socket.
 */
extern void FinisNotify(void)
{
   if (initialized) {
      close(s);
      remove(NotifyName());
   }
}
#<

#>            oshell.c
/* oshell.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It implements the Unix overlay shell.
Author: J.P. Terlouw.
*/

#include "stddef.h"
#include "stdlib.h"
#include "monopoly.h"
#include "oshell.h"

/* ========================================================================== */
/*                                 oshell                                     */
/* -------------------------------------------------------------------------- */
/*  Activate a subshell which overlays Hermes.
 */
extern void oshell(void)
{
   char *shell;
   shell = getenv("SHELL");
   if (!shell) shell = "/bin/csh";
   Monopoly(shell,NULL,NULL);
}
#<

#>            parmgr.c
/* parmgr.c
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

User input parameter administration module.

This module is private to Hermes.
Author: J.P. Terlouw.
*/

#include "stdlib.h"
#include "stdio.h"
#include "string.h"

#include <sys/file.h>

#include "hermescom.h"
#include "bintree.h"
#include "callback.h"
#include "parmgr.h"

#define LINEWIDTH  75       /* preferred maximum line width for keyword file */
#define LINESALL   20       /* number of lines to allocate by display_par    */
#define KEYTREE(x) ((Tree)&((Node)(x->keytree))->data)

typedef struct _Value {
   struct _Value *next;      /* pointer to next datum                         */
   struct _Value *rclfile;   /* pointer to any recall file contents           */
   int            rejected;  /* flag indicating that this datum is invalid    */
   char           text[1];   /* datum text (only space reserved for zero byte)*/
} *Value; /* ----------------------- User input value field ----------------  */

typedef struct _KeyNode {
   struct _Node keypart;
   Value        first;       /* first datum in linked list */
   Value        last;        /* last datum                 */
   Value        current;     /* current datum              */
   int          source;      /* pid of originating task    */
} *KeyNode; /* --------------------- User input parameter header ----------- */

typedef struct _AllPar {
   KeyNode   root;
   char      *buffer;
   int       width;
   int       line;
   int       col;
} *AllPar; /* --------------------- Information block for display_par c.s. - */

static void ReadFile(void);
static void KeyWrite(Node,void*);
static void TaskWrite(Node,void*);
static void WriteFile(void);
static void RemoveRclFile(Value);
static void ResetKey(Node,void*);
static void ClearKey(Node,void*);
static int  CheckRcl(TaskStatus,Value);
static void DisplayPar(Node,void*);
static void PutDisChar(char,AllPar);
static void CallParProc(TaskStatus);

static Node tasktree=NULL;                          /* binary tree with tasks */
static CallbackChain cblist=NULL;
static bool initialized=FALSE;
static bool changed;
static FILE *keyfile=NULL;

static _KeyEvent eventmsg;

/* ========================================================================== */
/*                            init_par                                        */
/* -------------------------------------------------------------------------- */
/*  init_par() initializes the user input parameter table for the specified
 *  task. If status->keep is set, the table is left intact, i.e. only garbage
 *  is removed. If status->keep is clear, all parameters will be removed.
 */
extern void init_par(TaskStatus status)
{
   if (!initialized) {
      ReadFile();
      initialized = TRUE;
   }
   status->keytree = RetrieveNode(&tasktree,status->name);
   if (status->keytree) {
      if (status->keep) {
         TraverseTree(KEYTREE(status),ResetKey,KEYTREE(status));
      } else {
         TraverseTree(KEYTREE(status),ClearKey,KEYTREE(status));
         changed = TRUE;
      }
      CallParProc(status);
   }
}

/* ========================================================================== */
/*                            get_par                                         */
/* -------------------------------------------------------------------------- */
/*  get_par() tries to locate the current parameter value associated with 'key'.
 *  If found, it returns a pointer to the value found, if not NULL.
 *  If a rejected value is found, the 'rejected' component of the task
 *  status block will point to the value text and NULL is
 *  returned.
 *  If a source task is associated with the keyword, this will be allowed
 *  to continue if it is either an event-driven task and the default code
 *  in the request contains the "DONE" code (8) or it is not event-driven.
 */
extern char *get_par(TaskStatus status, char *key)
{
   KeyNode    knode;
   Value      val;
   UserinpRequest request=(UserinpRequest)&(status->request);
   int        done = (request->level&8) || (!status->proc->fd_notify);

   if (!status->keytree) return NULL;

   knode = (KeyNode)RetrieveNode(KEYTREE(status),key);

   if (done && knode && knode->source) {         /* keyword from other task? */
      TaskStatus source=pidtask(knode->source);
      if (source) {                              /* still alive?             */
         source->waitkeyread--;                  /* decrement keyword count  */
         if (!source->waitkeyread) {             /* all keywords read?       */
            reply_send(source, 0);               /* other task may continue  */
         }
      }
      knode->source = 0;                         /* clear other task's pid   */
   }

   if (knode && (val=knode->current)) {
      if (CheckRcl(status,val)) {
         eventmsg.code = KEYREJECT;   
         strcpy(eventmsg.key,key);
         NotifyTask(status, (char*)&eventmsg, sizeof(eventmsg));
         return NULL;
      }
      if (val->rclfile) val=val->rclfile;
      if (val->rejected) {
         status->rejected = val->text;
         return NULL;
      }
      return val->text;
   } else {
      return NULL;
   }
}

/* ========================================================================== */
/*                            display_par                                     */
/* -------------------------------------------------------------------------- */
/*  display_par() returns a pointer to a dynamic block of characters containing
 *  a 'displayable' representation of a task's user input parameters.
 *
 *  Arguments:        name   --  the task name;
 *                    width  --  the display width.
 *
 *  Returns:                     a character pointer. It is the responsibility
 *                               of the caller to free the memory.
 */
extern char *display_par(char *name, int width)
{
   AllPar par = Calloc(1,sizeof(struct _AllPar));
   char   *result;
   Node   taskhead;
   int i;

   if (!initialized) {
      ReadFile();
      initialized = TRUE;
   }
   taskhead = RetrieveNode(&tasktree,name);
   par->buffer = Malloc(LINESALL*width+1);
   par->width = width;
   if (taskhead) {
      par->root = taskhead->data;
      TraverseTree((Tree)&par->root,DisplayPar,par);
   }
   for (i=0; i<width; i++) PutDisChar('=',par);
   PutDisChar(0,par);
   result = par->buffer;
   free(par);
   return result;
}

/* ========================================================================== */
/*                            insert_par                                      */
/* -------------------------------------------------------------------------- */
/*  insert_par() inserts a parameter value in the parameter table.
 *  If there still is a valid current parameter value, its complete contents
 *  will be replaced by the new value.
 *  If there is an invalid current parameter value, the new value will be
 *  inserted in place of the invalid value.
 *  In other cases, the new value will be appended to the list of previous
 *  values.
 *  The 'source' argument indicates the sender of the keyword.
 */
extern void insert_par(TaskStatus status, char *key, char *value, int source)
{
   KeyNode knode;
   Value   val, next;
   Value   *link;
   char    *vstart=value;
   int     again=1;

                    /* Check whether task is already present in the task tree */
   if (!status->keytree) {
      status->keytree = Calloc(1,sizeof(struct _Node));
      strcpy(((Node)(status->keytree))->name,status->name);
      InsertNode(&tasktree,(Node)(status->keytree));
   }

                                             /* Obtain any existing parameter */
   knode = (KeyNode)RetrieveNode(KEYTREE(status),key);
    
   if (knode && knode->source) source = knode->source; /*Sender still waiting*/

                                          /* Condition for complete replace ? */
   if (knode && knode->current) {
      if (knode->current->rejected) {
         for ( link  = &knode->first;
              *link != knode->current;
               link  = &(*link)->next) {;}
         next = knode->current->next;
         free(knode->current);
         knode->current = NULL;
      } else if (knode->current->rclfile && knode->current->rclfile->rejected) {
         link = &knode->current->rclfile;
         next = (*link)->next;
         free(*link);
      } else {
         ClearKey((Node)knode,KEYTREE(status));
         knode = NULL;
      }
   } else if (knode) {
      link = &(knode->last->next);
      next   = NULL;
   }

                                /* (Re-) create parameter if it doesn't exist */
   if (!knode) {
      knode = Calloc(1,sizeof(struct _KeyNode));
      strcpy(((Node)knode)->name,key);
      InsertNode(KEYTREE(status),(Node)knode);
      link = &knode->first;
      next   = NULL;
   }
   knode->source = source;                      /* register sender task */
   while (again) {
      int quoted = 0;
      again = 0;
      vstart = value;
      while (*vstart==' ') vstart++;            /* trim leading blanks  */
      while (*value) {
         if (*value=='`') quoted = !quoted;
         if (*value==';' && !quoted) {
            char *back=value;
            while (*(--back)==' ') *back=0;     /* trim trailing blanks */
            again = 1;
            *value = 0;
            value++;
            break;
         }
         value++;
      }
      val = Calloc(1,sizeof(struct _Value)+strlen(vstart));
      strcpy(val->text,vstart);
      *link =  val;
      val->next = next;
      link  = &val->next;
      if (!next) knode->last = val;
      if (!knode->current) knode->current = val;
   }
   changed = TRUE;
   status->rejected = NULL;                    /* clear any rejected input */
   CallParProc(status);
   eventmsg.code = KEYCHANGE;
   strcpy(eventmsg.key,key);
   NotifyTask(status, (char*)&eventmsg, sizeof(eventmsg));
}


/* ========================================================================== */
/*                            cancel_par                                      */
/* -------------------------------------------------------------------------- */
/*  cancel_par() cancels the current parameter value and moves the current
 *  value pointer to any next value in the list.
 *  If the current value originates from a recall file, the memory associated
 *  with it will be released.
 */
extern void cancel_par(TaskStatus status, char *key)
{
   KeyNode knode;
   Value   val;

   if (status->keytree) {
      knode = (KeyNode)RetrieveNode(KEYTREE(status),key);
      if (knode && (val=knode->current)) {
         if (val->rclfile) {
            val=val->rclfile;
            knode->current->rclfile = val->next;
            free(val);
         }
         if (!knode->current->rclfile) knode->current = knode->current->next;
         CallParProc(status);
         changed = TRUE;
         eventmsg.code = KEYCANCEL;
         strcpy(eventmsg.key,key);
         NotifyTask(status, (char*)&eventmsg, sizeof(eventmsg));
      }
   }
}

/* ========================================================================== */
/*                            reject_par                                      */
/* -------------------------------------------------------------------------- */
/*  reject_par() flags the current parameter value as invalid.
*/
extern void reject_par(TaskStatus status, char *key)
{
   KeyNode knode;

   if (status->keytree) {
      knode = (KeyNode)RetrieveNode(KEYTREE(status),key);
      if (knode && knode->current) {
         if (knode->current->rclfile)
            knode->current->rclfile->rejected = 1;
         else
            knode->current->rejected = 1;
         eventmsg.code = KEYREJECT;
         strcpy(eventmsg.key,key);
         NotifyTask(status, (char*)&eventmsg, sizeof(eventmsg));
      }
   }
}

/* ========================================================================== */
/*                            close_par                                       */
/* -------------------------------------------------------------------------- */
/*  close_par() writes all parameters of all tasks to the parameter file.
*/
extern void close_par(void)
{
   changed = TRUE;
   WriteFile();
}

/* ========================================================================== */
/*                            update_par                                      */
/* -------------------------------------------------------------------------- */
/*  If the 'changed' flag is set, update_par() writes all parameters of
 *  all tasks to the parameter file.
 */
extern void update_par(void)
{
   WriteFile();
}

/* ========================================================================== */
/*                            write_par                                       */
/* -------------------------------------------------------------------------- */
/*  write_par writes all parameter data of the given task to the specified
 *  file. The only possible error condition arises when it is impossible to
 *  write to the file; when no parameter data is available, an empty file
 *  is created.
 */
extern int write_par(char *taskname, char *filename)
{
   Node tasknode;
   Tree keytree=NULL;

   if (!initialized) {
      ReadFile();
      initialized = TRUE;
   }
   tasknode = RetrieveNode(&tasktree,taskname);
   if (tasknode)
   keytree =(Tree)&(tasknode->data);
   keyfile = fopen(filename,"w+");
   if (keyfile) {
      if (keytree) TraverseTree(keytree,KeyWrite,NULL);
      fclose(keyfile);
      return 0;
   } else return -1;
}
/* ========================================================================== */
/*                            write_rcl                                       */
/* -------------------------------------------------------------------------- */
/* write_rcl writes the contents of a parameter to a file. It can also be used
 * to test the presence of a taskname or keyword.
 */
extern int write_rcl(char *taskname, char *key, char *filename)
{
   Node taskhead;
   KeyNode knode;
   Value v;
   FILE *rclfile;

   taskhead = RetrieveNode(&tasktree,taskname);
   if (!taskhead) return -1;
   if (!key) return 0;                  /* end of test taskname-present phase */
   knode = (KeyNode)RetrieveNode((Tree)&taskhead->data,key);
   if (!knode) return -2;
   if (!filename) return 0;             /* end of test keyword-present phase  */
   rclfile = fopen(filename,"w+");
   if (!rclfile) return -3;
   for (v=knode->first; v; v=v->next) fprintf(rclfile,"%s\n",v->text);
   fclose(rclfile);
   return 0;
}

/* ========================================================================== */
/*                           InsertParProc                                    */
/* -------------------------------------------------------------------------- */
/* InsertParProc registers a procedure to be called whenever any parameter
 * value has changed. The prototype of 'proc' is:
 *
 *    void proc(
 *             int id,         // the identification of the registration
 *             char *taskname, // the taskname for which a parameter changed
 *             void *arg);     // the argument arg when calling InsertParProc
 *
 */
extern long InsertParProc(ParProc proc, void *arg)
{
   if (!cblist) cblist = NewCbChain();
   return (long)RegisterCb(cblist,(CallbackProc)proc,arg);
}

/* ========================================================================== */
/*                           RemoveParProc                                    */
/* -------------------------------------------------------------------------- */
/* RemoveParProc removes the registration of a procedure registered by
 * calling InsertParProc.
 */
extern void RemoveParProc(long id)
{
   DeregisterCb(cblist,(CallbackId)id);
}

/* -------------------------------------------------------------------------- */
/*                               CallParProc                                     */
/* -------------------------------------------------------------------------- */
/* CallParProc() calls all registered parameter change handlers.
 */
static void CallParProc(TaskStatus task)
{
   InvokeCb(cblist,task->name);
}

/* -------------------------------------------------------------------------- */
/*                               ReadFile                                     */
/* -------------------------------------------------------------------------- */
/*  ReadFile() reads any parameter ('macro') file and inserts the contents
 *  in the appropriate structures.
 */
static void ReadFile(void)
{
   struct _TaskStatus task;                         /* fake task status block */
   int     i, j, size;
   long    cmdid;
   char    *contents, *start, *key, *value;

   task.proc = NULL;
   contents = FileContents("HERMES.keywd",&size);
   if (contents) {
      i = 0;
      while (i<size) {
                                                  /* start of task partition? */
         if (contents[i++]=='#' && contents[i]=='>') {
            i++;
            j = 0;
                           /* copy task name (without extension) to task node */
            while (contents[i]!='\n' && strncmp(contents+i, ".key\n", 5)) {
               task.name[j++] = contents[i++];
            }
            task.name[j] = 0;
            task.keytree = NULL;
            StringUp(task.name);
            do ; while (contents[i++]!='\n');            /* skip to next line */
            start = &contents[i];
            while (i<size && !(contents[i++]=='#' && contents[i]=='<')) {;}
            contents[i-1] = 0;
            if (ParseCmd(start)==NOTSKNAME) {
               cmdid = CmdGetInfo();
               for (;;) {
                  CmdParam(cmdid,&key,&value);
                  if (!key) break;
                  insert_par(&task,key,value,0);
               }
               CmdDelete(cmdid);
            }
         }
      }
      free(contents);
   }
   changed = FALSE;
}

/* -------------------------------------------------------------------------- */
/*                               WriteFile                                    */
/* -------------------------------------------------------------------------- */
/*  If the 'changed' flag is set, WriteFile() dumps the whole parameter
 *  administration to the parameter file.
 */
static void  WriteFile(void)
{
   if (initialized && changed) {
      keyfile = fopen("HERMES.keywd","w+");
      if (keyfile) {
         rewind(keyfile);
         TraverseTree(&tasktree,TaskWrite,NULL);
         fclose(keyfile);
         changed = FALSE;
      }
   }
}

/* -------------------------------------------------------------------------- */
/*                               TaskWrite                                    */
/* -------------------------------------------------------------------------- */
/*  TaskWrite() writes the name of a task node to the parameter file;
 *  then calls TraverseTree to write all keyword nodes and finally writes
 *  a terminator record.
 *  If the keyword tree is empty, this function does nothing.
 */
static void TaskWrite(Node node, void *arg)
{
   if (node->data) {
      fprintf(keyfile,"#>%s.key\n",node->name);
      TraverseTree((Tree)&node->data,KeyWrite,NULL);
      fprintf(keyfile,"#<\n");
   }
}

/* -------------------------------------------------------------------------- */
/*                              KeyWrite                                      */
/* -------------------------------------------------------------------------- */
/*  KeyWrite() writes the text of a keyword node to the parameter file.
 */
static void KeyWrite(Node node, void *arg)
{
   KeyNode knode=(KeyNode)node;
   Value   v;
   int     width=0;

   if (knode->current) {
      fprintf(keyfile,"%s ",node->name);
      width += strlen(node->name)+1;
      v = knode->first;
      while(v) {
         width += strlen(v->text);
         if (width>LINEWIDTH) {
            width = 0;
            fprintf(keyfile,"\n  ");
         }
         fprintf(keyfile,"%s",v->text);
         if ((v=v->next)) fprintf(keyfile," ; ");
      }
      fprintf(keyfile,"\n");
   }
}

/* -------------------------------------------------------------------------- */
/*                              RemoveRclFile                                 */
/* -------------------------------------------------------------------------- */
/*  RemoveRclFile removes any recall file contents which may be associated
 *  with a keyword.
 */
static void RemoveRclFile(Value v)
{
   Value r, i;

   if (v) {
      r=v->rclfile;
      while(r) {
         i = r->next;
         free(r);
         r = i;
      }
      v->rclfile = NULL;
   }
}

/* -------------------------------------------------------------------------- */
/*                                ResetKey                                    */
/* -------------------------------------------------------------------------- */
/*  ResetKey() removes cancelled keywords for a given keyword tree and resets
 *  the current position for the remaining keywords and removes any
 *  recall file contents.
 */
static void ResetKey(Node node, void *tree)
{
   KeyNode knode=(KeyNode)node;
   Value   v;

   if (knode->current) {                         /* not completely cancelled? */
      knode->current = knode->first;
      for (v=knode->first; v; v=v->next) {
         v->rejected = 0;
         RemoveRclFile(v);
      }
   } else ClearKey(node, tree);         /* completely cancelled: remove entry */
}

/* -------------------------------------------------------------------------- */
/*                              ClearKey                                      */
/* -------------------------------------------------------------------------- */
/*  ClearKey() completely removes a parameter entry.
 */
static void ClearKey(Node node, void *tree)
{
   KeyNode knode=(KeyNode)node;
   Value   v, t;

   v=knode->first;
   while (v) {
      t = v->next;
      RemoveRclFile(v);
      free(v);
      v = t;
   }
   free(UnlinkNode(tree,node->name));
}

/* -------------------------------------------------------------------------- */
/*                               CheckRcl                                     */
/* -------------------------------------------------------------------------- */
/*  CheckRcl checks whether a recall file is specified for a parameter value,
 *  and if so, reads that recall file.
 */
static int CheckRcl(TaskStatus status, Value val)
{
#define RCLLINLEN 500
#define MAXLINES  10000
   FILE *rclfile;
   char filename[50];
   char line[RCLLINLEN+1];
   int  rangecode;
   int  i, start=0, end=0;
   char *vstart, *vend;
   Value rclval,*link;

   if (val->rclfile) return 0;
   if (val->text[0]=='<') {
      int fs;
      for (fs=1; val->text[fs]==' '; fs++) {}
      link = &val->rclfile;
      for (i=0; val->text[i+fs] && val->text[i+fs]!=' '; i++) {
         filename[i] = val->text[i+fs];
      }
      filename[i] = 0;
      strcat(filename,".rcl");
      rclfile = fopen(filename,"r");
      if (!rclfile) {
         val->rejected = 1;
         err_uca(status,"Cannot open recall file");
         status->rejected = val->text;
         return -1;
      }
      start = 1; end = MAXLINES;
      rangecode = dcdrange(&val->text[i+fs],&start,&end);
      if (rangecode<0 || start>end) {
         fclose(rclfile);
         val->rejected = 1;
         err_uca(status,"Range syntax error");
         status->rejected = val->text;
         return -1;
      }
      for (i=1; i<start; i++) (void)fgets(line,RCLLINLEN,rclfile);
      while (start<=end) {
         (void)fgets(line,RCLLINLEN,rclfile);
         if (feof(rclfile)) break;
         vstart = line;
         while (*vstart==' ') vstart++;           /* trim leading blanks      */
         line[strlen(line)-1] = 0;                /* remove newline character */
         vend = line + strlen(line);         
         while (*(--vend)==' ') *vend = 0;        /* trim trailing blanks     */
         rclval = Calloc(1,sizeof(struct _Value)+strlen(vstart));
         strcpy(rclval->text,vstart);
         *link = rclval;
         link = &rclval->next;
         start++;
      }
                                          /* insert at least one empty record */
      if (!val->rclfile) val->rclfile = Calloc(1,sizeof(struct _Value));
      fclose(rclfile);
#if 0
/*+++ This call disrupts normal tHermes operation when a recall file is
 *    specified at a keyword prompt.
 *    Just removing it causes the first value of a recall file to be 'missed'
 *    by the showkeys display. If this is not acceptable, the parameter
 *    change callback should be extended with a 'reason' argument to be
 *    checked by clients which want to call satisfy (do_request).
 */
      CallParProc(status);
#endif
      return 0;
   } else return 0;
}

/* -------------------------------------------------------------------------- */
/*                              DisplayPar                                    */
/* -------------------------------------------------------------------------- */
/*  DisplayPar() writes a displayable representation of a userinp parameter
 *  to the structure specified in the second argument.
 */
static void DisplayPar(Node node, void *arg)
{
   AllPar par=(AllPar)arg;

   KeyNode knode = (KeyNode)node;
   char   *c;
   Value  v;
   int    i;

   c = node->name;
   while (*c) PutDisChar(*(c++),par);
   PutDisChar(' ',par);

   v = knode->current;
   if (v) {
      while (v) {
         char *c=v->text;
         while (*c) PutDisChar(*(c++),par);
         if (v->rclfile) {
            PutDisChar(' ',par); PutDisChar('(',par);
            c = v->rclfile->text;
            while (*c) PutDisChar(*(c++),par);
            PutDisChar(')',par);
         }
         if ((v=v->next)) {
            c = " ; ";
            while (*c) PutDisChar(*(c++),par);
         }
      }
   } else {
      c = "(cancelled)";
      while (*c) PutDisChar(*(c++),par);
   }
   for (i=par->col; i<par->width; i++) PutDisChar(' ',par); /* fill last line */
}

/* -------------------------------------------------------------------------- */
/*                             PutDisChar                                     */
/* -------------------------------------------------------------------------- */
/*  PutDisChar() writes one character to the
 *  structure specified in the second argument.
 */
static void PutDisChar(char c, AllPar par)
{
   if (par->col==0) {
      if (!(par->line%LINESALL)) {
         if (par->buffer)
         par->buffer = Realloc(par->buffer,par->width*(par->line+LINESALL)+1);
         else
         par->buffer = Malloc(par->width*(par->line+LINESALL)+1);
      }
   }
   par->buffer[par->line*par->width+(par->col++)] = c;
   if (par->col==par->width) {
      par->col = 0;
      par->line++;
   }
}

/*
   REMARK: Though using a binary tree for parameter data is cute and was a
           nice exercise in programming, it is not as efficient as it seems
           at first sight. When the parameter file is written, this is
           done in alphabetical keyword order. When this file is read
           back, the resulting binary tree will be degenerate and becomes
           effectively a linked list. This does however not seem to be a
           problem at present.
*/
#<

#>            parsekeyhelp.c
/* parsekeyhelp.c
                              COPYRIGHT (c) 1993
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It is responsible for parsing the
userinp keyword help.

Author: J.P. Terlouw
*/

#include "string.h"
#include "parsekeyhelp.h"


/* ========================================================================== */
/*                               ParseKeyHelp                                 */
/* -------------------------------------------------------------------------- */
/*  ParseKeyHelp parses the supplied help text and depending on what elements
 *  are encountered calls the specified routines.
 */
extern void ParseKeyHelp(char *text,                 /* the help text */
                         KhPutWord PutWordPtr,       /* process a word */
                         KhNewLine NewLinePtr,       /*   a newline */
                         KhPutRefWord PutRefWordPtr, /*   a 'reference word' */
                         KhPutReference PutReferencePtr) /*  a reference */
{
   char *result;
   char *notfound="(no text)";
   char reference[50];
   int length=0;
   int nlseen=1;
   int inword=0;
   int refstate=0;
   result = text;

   if (!text) {
      (*PutWordPtr)(notfound,strlen(notfound));
      return;
   }
   while (*text) {
      switch (*text) {

         case '\n': {
            if (refstate==1) {
               refstate = 0;
               inword++;
               length++;
            }
            text++;
            if (inword) {
               (*PutWordPtr)(result,length);
               inword = 0;
            }
            if (nlseen) (*NewLinePtr)();
            length = 0;
            result = text;
            nlseen = 1;
            break;
         }

         case ' ': {
            if (refstate==1) {
               refstate = 0;
               length++;
               inword++;
            }
            if (nlseen) {
               (*NewLinePtr)();
               nlseen = 0;
            }
            if (inword) {
               (*PutWordPtr)(result,length); length = 0; result = text;
               inword = 0;
            }
            while (*text && *text==' ') {
               length++;
               text++;
            }
            break;
         }

         case '%': {
            switch (refstate) {
               case 0: refstate++; text++; break;
               case 1: {
                  int i;
                  refstate = 0;
                  text++;
                  result = text;
                  length = 0;
                  while (*(text++) != '%') length++;
                  if (length) (*PutRefWordPtr)(result,length);
                  length = 0;
                  for (i=0; text[i] && text[i]!='%'; i++) {
                     reference[i] = text[i];
                  }
                  reference[i] = '\0';
                  (*PutReferencePtr)(reference);
                  text += i+1;
                  result = text;
                  break;
               }
            }
            break;
         }

         default: {
            if (nlseen && *text=='#') {
               while (*(text++)!='\n') {}
               result = text;
               break;
            }
            if (refstate==1) {
               refstate = 0;
               length++;
            }
            nlseen = 0;
            inword++;
            length++;
            text++;
         }
      }
   }
   if (refstate) inword++;
   if (inword) (*PutWordPtr)(result,length);
}

#<

#>            printmgr.c
/* printmgr.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It is responsible for the tHermes print
options.

Author: J.P. Terlouw
*/

#include <unistd.h>

#include "stddef.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "stdio.h"
#include "thermescom.h"
#include "printmgr.h"
#include "prompter.h"
#include "gipsyc.h"
#include "prntrnum.h"
#include "prntrcom.h"
#include "prntrdim.h"
#include "prntrnam.h"
#include "prntract.h"
#include "nelc.h"

extern TaskStatus Template;

static Window win=NULL;
static int   printnum=0, tmpnum=0, nprinters=0;
static int   printfile=0;
static bool  good;
static long  kid=0;
static long  fileprompter=0;
static char* currentfile=NULL;
static char  delayedfile[80]="";

static void deactivate(void);
static int  getchars(long,int,void*);
static void updatewin(void);
static int  setfile(long id, int reason, char *filename, void *arg);

/* ========================================================================== */
/*                               SelectPrinter                                */
/* -------------------------------------------------------------------------- */
/*
 *  SelectPrinter activates a menu-like window from which a printer for the
 *  screen hardcopy command can be selected.
 */
extern void SelectPrinter(void)
{
   extern int COLS;
   fint   num;

   if (kid) return;                                /* allow only one instance */
   win = CreateWindow(4,COLS);
   win->framed = TRUE;
   PutString(win,0,4,"Select printer:");
   PutString(win,3,4,
             "P: previous  --  N: next  --  F: file  --  ENTER: select");
   num=prntrnum_c();
   if (num>0) {
      nprinters = num;
   } else {
      nprinters = 1;              /* updatewin will eventually report problem */
   }
   if (printnum) tmpnum = printnum;
   else if (!tmpnum) tmpnum = 1;
   updatewin();
   PutCursor(win,1,1);
   MapWindow(win,0,0);
   kid = InsertKeyboardProc(getchars,NULL);
}

/* ========================================================================== */
/*                               SetPrinter                                   */
/* -------------------------------------------------------------------------- */
/*
 *  SetPrinter checks whether the specified printer number is allowable
 *  and, if so, selects that printer.
 */
extern int SetPrinter(int pno)
{
   fint r, ncol, nrow;

   r = prntrdim_c((fint*)&pno,&ncol,&nrow); /* check whether printer is known */
   if (!r) printnum = pno;
   return (int)r;
}

/* ========================================================================== */
/*                               PrintFile                                    */
/* -------------------------------------------------------------------------- */
/*
 *  PrintFile prints the file specified in its argument on the printer
 *  selected by SelectPrinter. If no printer is selected, the file name is
 *  saved and SelectPrinter is called. The latter then will print the file
 *  as soon as a printer is selected.
 */
extern int PrintFile(char *file)
{
   fint discard=1;


   if (printfile) {
      int  size;
      char *contents=FileContents(file, &size);
       
      write(printfile,contents,size);
      Delete(contents);
      remove(file);
      return 0;
   }
   
   if (printnum) {
      fint r;
      int tmpout, tmperr;
      tmpout = dup(1); close(1);
      tmperr = dup(2); close(2);
      r = prntract_c((fint*)&printnum,tofchar(file),&discard);
      (void)dup2(tmpout,1);
      (void)dup2(tmperr,2);
      return (int)r;
   } else {
      strcpy (delayedfile,file);
      SelectPrinter();
      return 0;
   }
}

/* -------------------------------------------------------------------------- */
/*                              deactivate                                    */
/* -------------------------------------------------------------------------- */
/*  deactivate() removes select printer menu.
 */
static void deactivate(void)
{
   DeleteWindow(win);
   RemoveKeyboardProc(kid);
   kid = 0;
   if (fileprompter) {
      DeletePrompter(fileprompter);
      fileprompter = 0;
   }
   *delayedfile = 0;
}

/* -------------------------------------------------------------------------- */
/*                                getchars                                    */
/* -------------------------------------------------------------------------- */
/*  keyboard handler getchars() allows the user to page through the printers
 *  and to select one.
 */
static int getchars(long id, int c, void *arg)
{
   if (c==QRESCHED) return YRESCHED;
   if (c==XRESCHED) {
      PutCursor(win,1,1);
      MapWindow(win,-1,-1);
      RefreshWindow(win);
      return 0;
   }

   if (c==CTRL_C) {
      deactivate();
      return 0;
   }
   
   if (c==WEBHELP) {
      if (WebHelp("*hermes/COA.html#printing")) return c;
      return 0;
   }

   if (c==CR) {
      if (good) {
         printnum = tmpnum;
         if (printfile) {
            close(printfile);
            printfile = 0;
            put_coa(Template,"<USER> Hardcopy redirection cancelled");
            put_log(Template,"<USER> Hardcopy redirection cancelled");
         }
         if (*delayedfile) {
            PutLine(win,1,"     Printing...");
            PutLine(win,2," ");
            RefreshWindow(win);
            PrintFile(delayedfile);
         }
      }
      deactivate();
      return 0;
   }

   if (c==UP || c==PAGEUP) c = 'P';
   if (c==DOWN || c==PAGEDOWN) c = 'N';
   if (isalpha(c)) {
      switch (toupper(c)) {
         case 'K':
         case 'P': tmpnum = (tmpnum+nprinters-2)%nprinters+1; break;
         case 'J':
         case 'N': tmpnum = tmpnum%nprinters+1; break;
         case 'F': fileprompter = Prompter(
                                   "Print to file:                          ",
                                   currentfile, setfile, NULL, 1,1); break;
         default : Beep(); return 0;
      }
      updatewin();
      RefreshWindow(win);
      return 0;
   } else return c;
}

/* -------------------------------------------------------------------------- */
/*                                 updatewin                                  */
/* -------------------------------------------------------------------------- */
/*  updatewin() paints the window for the printer currently being considered.
 */
static void updatewin(void)
{
   char  line[80];
   char  pcommb[50];
   char  pnameb[16];
   fchar pcomm;
   fchar pname;
   fint  ncol, nrow;
   fint  r;

   pcomm.a = pcommb; pcomm.l = sizeof( pcommb );
   pname.a = pnameb; pname.l = sizeof( pnameb );
   r  = prntrnam_c( (fint*)&tmpnum, pname );
   if (!r) r = prntrcom_c( (fint*)&tmpnum, pcomm );
   if (!r) r = prntrdim_c( (fint*)&tmpnum, &ncol, &nrow );
   if (r) {
      good = FALSE;
      sprintf(line,"  Error in printer database access, code: %d",r);
      PutLine(win,1,line);
      PutLine(win,2," ");
   } else {
      good = TRUE;
      sprintf(line,"  %3.0d: %.*s   (%d columns by %d lines)",
              tmpnum, (int)nelc_c(pname), pname.a, ncol, nrow);
      if (tmpnum==printnum) strcat(line,"   -- SELECTED --");
      PutLine(win,1,line);
      sprintf(line,"       %.*s",(int)nelc_c(pcomm), pcomm.a);
      PutLine(win,2,line);
   }
}

/* -------------------------------------------------------------------------- */
/*                                 setfile                                    */
/* -------------------------------------------------------------------------- */
/*  setfile tries to redirect hardcopy printing to specified file.
 */
#include <sys/file.h>
#ifndef O_CREAT
#include <fcntl.h>
#endif
static int  setfile(long id, int reason, char *filename, void *arg)
{


   if (reason==PROMPT_CANCEL) {
      fileprompter = 0;                       /* prompter will delete itself */
      return 0;
   }
   if (printfile) close(printfile);
   if (!*filename) {
      /* empty string: cancel redirection */
      if (printfile) {
         put_coa(Template,"<USER> Hardcopy redirection cancelled");
         put_log(Template,"<USER> Hardcopy redirection cancelled");
      }
      printfile = 0;
      fileprompter = 0;                       /* prompter will delete itself */
      return 0;
   }
   printfile = open(filename, O_RDWR|O_CREAT|O_APPEND, 0666);
   if (printfile<=0) {
      printfile = 0;
      (void)SetPrompterError(id, "... file cannot be opened");
      return -1;
   } else {
      char message[120];
      sprintf(message,"<USER> Hardcopies redirected to: %s", filename);
      put_coa(Template, message);
      put_log(Template, message);
      if (currentfile) Delete(currentfile);
      currentfile = StrDup(filename);
      printnum = 0;
      if (*delayedfile) {
         PutLine(win,1,"     Printing to file ...");
         PutLine(win,2," ");
         RefreshWindow(win);
         PrintFile(delayedfile);
      }
      fileprompter = 0;                       /* prompter will delete itself */
      deactivate();
   }
   return 0;
}
#<

#>            prompter.c
/* prompter.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes.
Author: J.P. Terlouw.
*/

#define TRUE  (1)
#define FALSE (0)

#include "stddef.h"
#include "ctype.h"
#include "string.h"
#include "alloc.h"
#include "windows.h"
#include "keyboard.h"
#include "keysym.h"
#include "prompter.h"

#define CURSOR PutCursor(pe->win,1,pe->typepos+2);RefreshWindow(pe->win);

typedef struct _PromptEnv {
   char   *prompt;          /* prompt message */
   char   *buffer;          /* typein buffer */
   int     bufsize;         /* typein buffer size */
   int     typepos;         /* current typing position */
   int     nchars;          /* number of characters typed */
   char   *dflt;            /* default string (initial contents of buffer) */
   char   *actmsg;          /* message during call to 'action' */
   char   *errmsg;          /* message when 'action' returns non-zero */
   int     y,x;             /* prompter position */
   Window  win;             /* prompter window */
   PrompterProc action;     /* action procedure */
   int     active;          /* indicates whether 'action' is being called */
   void   *arg;             /* action procedure argument */
   long    kbid;            /* keyboard handler id */
   int     error;           /* error indicator */
   char   *help;            /* relative URL for web help */
} *PromptEnv, _PromptEnv;


static int getchars(long,int,PromptEnv);

static char defhelp[]="*hermes/prompters.html";

/* ========================================================================== */
/*                            CreatePrompter                                  */
/* -------------------------------------------------------------------------- */
/*  CreatePrompter() creates a prompter.
 *
 *  Arguments:
 *      promptstring  -- the string to be displayed in the top line of
 *                       the prompter window.
 *      callback      -- pointer to function to be called whenever the user
 *                       types ENTER (RETURN) or CTRL_C.
 *      arg           -- argument to be passed to 'callback'.
 *
 *  Returns: an integer which uniquely identifies the prompter.
 *
 *  The prototype of 'callback' is:
 *
 *  int callback(int id,         // prompter id
 *               int status,     // prompter status: either PROMPT_ENTER
 *                               // or PROMPT_CANCEL
 *               char *result,   // string with current user type-in
 *               void *arg)      // the argument specified in SetPrompterProc().
 *
 *  It should return zero when it has performed its function successfully
 *  (at its own discretion), whereafter the prompter will be deleted
 *  automatically, or non-zero in case of errors, which will cause any
 *  error string to be displayed while the prompter remains active, allowing
 *  the user a chance to correct his input.
 */
extern long CreatePrompter(char *promptstring, PrompterProc callback, void *arg)
{
   PromptEnv pe=New(_PromptEnv);

   pe->bufsize = strlen(promptstring);
   pe->prompt = Malloc(pe->bufsize+1);
   strcpy(pe->prompt,promptstring);
   pe->action = callback;
   pe->arg    = arg;
   pe->help   = defhelp;
   return (long)pe;
}

/* ========================================================================== */
/*                            DeletePrompter                                  */
/* -------------------------------------------------------------------------- */
/*  DeletePrompter() deletes the specified prompter.
 */
extern void DeletePrompter(long id)
{
   PromptEnv pe=(PromptEnv)id;

   if (pe->buffer) Free(pe->buffer);
   if (pe->dflt)Free(pe->dflt);
   if (pe->actmsg) Free(pe->actmsg);
   if (pe->errmsg) Free(pe->errmsg);
   if (pe->win)    DeleteWindow(pe->win);
   if (pe->kbid)   RemoveKeyboardProc(pe->kbid);
   Free(pe->prompt);
   Free(pe);
}

/* ========================================================================== */
/*                            SetPrompterPos                                  */
/* -------------------------------------------------------------------------- */
/*  SetPrompterPos() defines the position of the upper left corner of the
 *  prompter window relative to the upper left corner of the tewrminal screen.
 *  It returns the prompter id.
 */
extern long SetPrompterPos(long id, int y, int x)
{
   PromptEnv pe=(PromptEnv)id;
   pe->y = y;
   pe->x = x;
   if (pe->win) {
      pe->win->y = y;
      pe->win->x = x;
      if (pe->win->mapped) RedrawScreen();
   }
   return id;
}

/* ========================================================================== */
/*                            SetPrompterDefault                              */
/* -------------------------------------------------------------------------- */
/*  SetPrompterDefault() sets the default string to be displayed in the
 *  type-in line of the prompter window. It must be called before the
 *  prompter is activated, otherwise this call will have no effect.
 *  It returns the prompter id.
 */
extern long SetPrompterDefault(long id, char *defaultstring)
{
   PromptEnv pe=(PromptEnv)id;
   if (!pe->dflt) {
      pe->dflt = Malloc(strlen(defaultstring)+1);
      strcpy(pe->dflt,defaultstring);
   }
   return id;
}

/* ========================================================================== */
/*                            SetPrompterError                                */
/* -------------------------------------------------------------------------- */
/*  SetPrompterError() sets the error string to be displayed in the bottom
 *  line of the prompter window after the action
 *  routine returns with a non-zero value. It can be called at any time.
 *  It returns the prompter id.
 */
extern long SetPrompterError(long id, char *errorstring)
{
   PromptEnv pe=(PromptEnv)id;
   if (pe->errmsg) Free(pe->errmsg);
   pe->errmsg = Malloc(strlen(errorstring)+1);
   strcpy(pe->errmsg,errorstring);
   return id;
}

/* ========================================================================== */
/*                            SetPrompterActive                               */
/* -------------------------------------------------------------------------- */
/*  SetPrompterActive() sets the string to be displayed in the bottom line
 *  of the prompter window while the action routine is active.
 *  It can be called at any time.
 *  It returns the prompter id.
 */
extern long SetPrompterActive(long id, char *activestring)
{
   PromptEnv pe=(PromptEnv)id;
   if (pe->actmsg) Free(pe->actmsg);
   pe->actmsg = Malloc(strlen(activestring)+1);
   strcpy(pe->actmsg,activestring);
   if (pe->active) {
      pe->win->framed = FALSE;          /* ... convince PutLine to write line */
      PutLine(pe->win,2," ");
      pe->win->framed = TRUE;
      PutLine(pe->win,2," ");
      PutString(pe->win,2,1,pe->actmsg);
      RefreshWindow(pe->win);
   }

   return id;
}

/* ========================================================================== */
/*                            SetPrompterHelp                                 */
/* -------------------------------------------------------------------------- */
/*  SetPrompterHelp() sets the GIPSY WWW help page.
 *  The argument string must be in static storage.
 *  It returns the prompter id.
 */
extern long SetPrompterHelp(long id, char *document)
{
   PromptEnv pe=(PromptEnv)id;
   pe->help = document;
   return id;
}

/* ========================================================================== */
/*                            ActivatePrompter                                */
/* -------------------------------------------------------------------------- */
/*  ActivatePrompter() activates the specified prompter.
 *  It allocates the type-in buffer, creates and initializes the prompter
 *  window and starts accepting characters from the terminal keyboard.
 *  Before this routine is called, the action routine MUST have been specified
 *  by calling SetPrompterProc().
 *  ActivatePrompter() returns the prompter id.
 */
extern long ActivatePrompter(long id)
{
   PromptEnv pe=(PromptEnv)id;

   if (pe->dflt && strlen(pe->dflt)>pe->bufsize)
      pe->bufsize = strlen(pe->dflt);
   pe->buffer = Calloc(pe->bufsize+1,1);
   if (pe->dflt) strcpy(pe->buffer,pe->dflt);
   pe->typepos = strlen(pe->buffer);
   pe->nchars  = pe->typepos;
   pe->win     = CreateWindow(3,pe->bufsize+4);
   FrameWindow(pe->win);
   PutString(pe->win,0,2,pe->prompt);
   PutString(pe->win,1,2,pe->buffer);
   MapWindow(pe->win,pe->y,pe->x);
   CURSOR
   pe->kbid = InsertKeyboardProc(getchars,pe);
   return (long)pe;
}

/* ========================================================================== */
/*                                PrompterType                                */
/* -------------------------------------------------------------------------- */
/*  PrompterType() simulates the user's typing action into a prompter.
 */
extern void PrompterType(long id, char c)
{
   PromptEnv pe=(PromptEnv)id;
   (void)getchars(0, (int)c, pe);
}

/* ========================================================================== */
/*                               PromptValue                                  */
/* -------------------------------------------------------------------------- */
/*  PromptValue() returns a pointer to the type-in string buffer of the
 *  specified prompter.
 */
extern char *PromptValue(long id)
{
   PromptEnv pe=(PromptEnv)id;
   pe->buffer[pe->nchars] = '\0';
   return pe->buffer;
}

/* ========================================================================== */
/*                               Prompter                                     */
/* -------------------------------------------------------------------------- */
/*   Simple general-purpose prompter routine. It creates and activates a
 *   prompter according to the specified arguments.
 *
 *   Arguments:  prompt   --  prompt string;
 *               dflt     --  default string, may be NULL;
 *               callback --  pointer to action routine;
 *               arg      --  argument to 'callback';
 *               row, col --  position of the prompter window.
 *
 *   Returns:    Prompter --  the prompter id.
 */
extern long Prompter (char *prompt,
                     char *dflt,
                     PrompterProc callback,
                     void *arg,
                     int row, int col
                    )
{
   long id=CreatePrompter(prompt,callback,arg);
   if (dflt) (void)SetPrompterDefault(id, dflt);
   (void)SetPrompterPos(id,row,col);
   return ActivatePrompter(id);
}

/* -------------------------------------------------------------------------- */
/*                                getchars                                    */
/* -------------------------------------------------------------------------- */
/*
 *  Keyboard handler to fill the prompt buffer.
 */
static int getchars(long id, int c, PromptEnv pe)
{
   int WebHelp(char *);

   if (c==QRESCHED) return YRESCHED;
   if (c==XRESCHED) {
      MapWindow(pe->win,-1,-1);
      CURSOR
      return 0;
   }
   if (pe->error) {
      pe->error = 0;
      pe->win->framed = FALSE;         /* ... convince PutLine to write line */
      PutLine(pe->win,2," ");
      pe->win->framed = TRUE;
   }
   if (isprint(c)) {                     /* accept only printable characters */
      if (pe->bufsize > pe->nchars) {
         int i;
         for (i=pe->nchars; i>pe->typepos; i--) {
            pe->buffer[i] = pe->buffer[i-1];
            PutChar(pe->win,1,i+2,pe->buffer[i]);
         }
         PutChar(pe->win,1,pe->typepos+2,c);
         pe->buffer[pe->typepos] = c;
         pe->typepos++;
         pe->nchars++;
         CURSOR
      } else Beep();                     /* buffer full: protest */

   } else switch(c) {

      case CR:                           /* ready; enter string and clean up */
      pe->buffer[pe->nchars] = '\0';
      if (pe->actmsg) {
         pe->win->framed = FALSE;      /* ... convince PutLine to write line */
         PutLine(pe->win,2," ");
         pe->win->framed = TRUE;
         PutLine(pe->win,2," ");
         PutString(pe->win,2,1,pe->actmsg);
         RefreshWindow(pe->win);
      }
      pe->active = 1;
      if (!(*pe->action)((long)pe, PROMPT_ENTER, pe->buffer, pe->arg)) {
         DeletePrompter((long)pe);
         return 0;
      } else {
         pe->active = 0;
         pe->win->framed = FALSE;      /* ... convince PutLine to write line */
         PutLine(pe->win,2," ");
         pe->win->framed = TRUE;
         if (pe->errmsg) {
            pe->error = 1;
            PutString(pe->win,2,1,pe->errmsg);
            RefreshWindow(pe->win);
         }
      }
      break;

      case CTRL_A:                     /* move to start */
      pe->typepos = 0;
      CURSOR
      RefreshWindow(pe->win);
      break;

      case CTRL_B:                     /* one character back */
      if (pe->typepos > 0) {
         pe->typepos--;
         CURSOR
      }
      break;

      case CTRL_C:                     /* cancel the prompter */
      pe->buffer[pe->nchars] = '\0';
      (void)(*pe->action)((long)pe, PROMPT_CANCEL, pe->buffer, pe->arg);
      DeletePrompter((long)pe);
      return 0;

      case CTRL_D:                     /* forward delete */
      if (pe->typepos < pe->nchars) {
         int i;
         for (i=pe->typepos; i<(pe->nchars-1); i++) {
            pe->buffer[i] = pe->buffer[i+1];
            PutChar(pe->win,1,i+2,pe->buffer[i]);
         }
         pe->nchars--;
         PutChar(pe->win,1,pe->nchars+2,' ');
         CURSOR
      }
      break;

      case CTRL_E:                     /* move to end */
      pe->typepos = pe->nchars;
      CURSOR
      break;

      case CTRL_F:                     /* one character forward */
      if (pe->typepos < pe->nchars) {
         pe->typepos++;
         CURSOR
      }
      break;

      case CTRL_K:                     /* kill forward */
      if (pe->typepos < pe->nchars) {
         int i;
         for (i=pe->typepos; i<pe->nchars; i++) {
            PutChar(pe->win,1,i+2,' ');
         }
         pe->nchars = pe->typepos;
         CURSOR
      }
      break;

      case DEL:                       /* backward delete */
      if (pe->typepos >0) {
         int i;
         for (i=pe->typepos; i<pe->nchars; i++) {
            pe->buffer[i-1] = pe->buffer[i];
            PutChar(pe->win,1,i-1+2,pe->buffer[i]);
         }
         pe->typepos--;
         pe->nchars--;
         PutChar(pe->win,1,pe->nchars+2,' ');
         CURSOR
      }
      break;

      case CTRL_U:                 /* clear buffer */
      pe->nchars  = 0;
      pe->typepos = 0;
      CURSOR
      PutLine(pe->win,1," ");
      break;
       
      case WEBHELP:                /* context sensitive WWW help */
      if (WebHelp(pe->help)) return c;
      break;

      default:                     /* propagate unrecognized characters */
      return c;
   }
   RefreshWindow(pe->win);
   return 0;
}

#<

#>            put_log.c
/* put_log.c
                              COPYRIGHT (c) 1993
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

              'put_log.dc3'
Module:        put_log

Purpose:       Private to Hermes. Routines handling log file output.

File:          put_log.c

Author:        J.P. Terlouw

Use:           void put_log(status, line)
                    TaskStatus status;
                    char       *line

               put_log writes to the file GIPSY.LOG in Hermes' current
               directory. Locking is provided to allow multiple instances
               of Hermes.

Updates:       25-Oct-90  --  original document
	       21-Nov-91  --  adding flexibel logfile naming (M.A.M. Ruiter)
	        7-Dec-93  --  offer wait/no wait option when log file is opened

*/
#include "stddef.h"
#include "stdlib.h"
#include <sys/types.h>
#include <sys/file.h>
#ifndef	O_CREAT
#include <fcntl.h>
#endif
#include <sys/stat.h>
#if	defined(__aix__)
#include <sys/lockf.h>
#endif


#if defined(__sysv__) | defined(__APPLE__)
#include <unistd.h>
#endif

#include "hermescom.h"
#include "string.h"
#include "mainloop.h"

#define DEFLOG "GIPSY.LOG"      /* default name of logfile */

static char *log_name = NULL;
static int fd=-1;

static void syncer(int id, void *arg)
{
   fsync(fd);
}

static int testlocking(void)
{
   char testfile[20];
   int  tfd, tresult;
    
   sprintf(testfile, "hermeslock%d", getpid());
   tfd = open(testfile, O_CREAT|O_RDWR|O_APPEND, 0666 );
#if defined(__sysv__) && defined(F_LOCK)
   tresult = lockf(tfd, F_TLOCK, 0);
#else
   tresult = flock(tfd, LOCK_EX | LOCK_NB);
#endif
   close(tfd);
   remove(testfile);
   if (tresult) {
      int reply;
      printf("Locking unavailable. Proceed anyway? Y/N ");
      reply = getchar();
      if (reply!='y' && reply!='Y') exit(1);
   }
   return tresult;
}

int  open_log(int logwait)
{
   int result;
   
   if (fd<0) {
      if (!log_name) {
         log_name = (char *)Malloc((strlen(DEFLOG) + 1)*sizeof(char));
         strcpy(log_name, DEFLOG);
      }
      fd = open(log_name, O_CREAT|O_RDWR|O_APPEND, 0666 );
      if (testlocking()) {
         result = 0;                   /* user accepted unavailable locking */
      } else {
#if defined(__sysv__) && defined(F_LOCK)
         result = lockf(fd,logwait?F_LOCK:F_TLOCK,0);
#else
         result = flock(fd,LOCK_EX|(logwait?0:LOCK_NB));
#endif
      }
   }
   if (result) {
      close(fd);
      fd = -1;
   } else {
      (void)InsertTimerProc(syncer, 60, NULL);
   }
   return result;
}
   
void put_log(TaskStatus status, char *line)
{
   if (fd==0) return;
   if (status && !(status->devmask & LOGFILE)) return;
   if (fd<0) (void)open_log(1);
   if (fd==0) return;
   (void)write(fd,line,strlen(line));
   (void)write(fd,"\n",1);
}

void chlogname(char *lgname)     /* change logfile name */
{
   log_name = (char *)Malloc((strlen(lgname)+1)*sizeof(char));
   strcpy(log_name, lgname);
}
#<

#>            rdefaults.c
/*   rdefaults.c

                           COPYRIGHT (c) 1991, 2001
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes.
Author: J.P. Terlouw.
*/

#define GLOBALDEF "tasks.def"

#include "stddef.h"
#include "stdlib.h"
#include "string.h"
#include "hermescom.h"

/* -------------------------------------------------------------------------- */
/*                              insert                                        */
/* -------------------------------------------------------------------------- */
static int insert(TaskStatus task, char *contents)
{
   char *start, *key, *value;
   int  status;
   long cmdid;

   if (!contents) return 0;                             /* no file - no error */
   for (start = contents; *start==' '||*start=='\t'||*start=='\n'; start++);
   status = ParseCmd(start);
   free(contents);
   if (!status || status==NOTSKNAME) {
      cmdid = CmdGetInfo();
      for (;;) {
         /*+++ suspend ParProc's during this loop? */
         CmdParam(cmdid,&key,&value);
         if (!key) break;
         insert_par(task, key, value, 0);
      }
      CmdDelete(cmdid);
      return 0;
   } else return status;
}   

/* ========================================================================== */
/*                              rdefaults                                     */
/* -------------------------------------------------------------------------- */
/*   rdefaults() processes the default file associated with the task
 *   specified in the task argument.
 *   If the call is successful, i.e. if the contents of the recall file(s)
 *   is acceptable to ParseCmd(), OR no default file is present, then zero is
 *   returned. When there are errors in any default file, the return code
 *   of ParseCmd() is returned.
 *   Default file names have the format "taskname.def" or "tasks.def".
 *   The latter file contains defaults for all tasks. Task-specific defaults
 *   supersede any defaults for all tasks.
 *   Both the user's login directory and the current working directory are
 *   searched for default files. Defaults in the working directory
 *   supersede defaults in the login directory.
 */
extern int rdefaults(TaskStatus task)
{
   char name[NAMLEN+4+1];
   char filename[128];
   char *contents;
   int  size, status, result=0;

   strcpy(name,task->name);
   StringLow(name);
   strcat(name,".def");

   /*
    *  $HOME/tasks.def
    */
   strcpy(filename, getenv("HOME"));
   strcat(filename,"/.gipsy/");
   strcat(filename, GLOBALDEF);
   contents = FileContents(filename,&size);
   status = insert(task, contents);
   if (status) result = status;

   /*
    *  ./tasks.def
    */
   contents = FileContents(GLOBALDEF,&size);
   status = insert(task, contents);
   if (status) result = status;

   /*
    *  $HOME/<taskname>.def
    */
   strcpy(filename, getenv("HOME"));
   strcat(filename,"/.gipsy/");
   strcat(filename, name);
   contents = FileContents(filename,&size);
   status = insert(task, contents);
   if (status) result = status;

   /*
    *  ./<taskname>.def
    */
   contents = FileContents(name,&size);
   status = insert(task, contents);
   if (status) result = status;

   return result;
}
#<

#>            readcontext.c
/* readcontext.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

Private to hermes. Read the default task context, directory and path.

Author: J.P. Terlouw
*/

#define DEFFILE "hermes.def"

#include <unistd.h>

#include "stddef.h"
#include "stdlib.h"
#include "string.h"
#include "hermescom.h"
#include "filecontents.h"
#include "parse.h"
#include "execpath.h"
#include "taskmgr.h"
#include "changecase.h"
#include "help.h"
#include "alloc.h"
#include "readcontext.h"

int SetPrinter(int);
int SelectTek(char *);

extern int NTASK;
static void ProcessFile(char* defaults, TaskStatus template);
static void ReportError(char*);
static int  errors=0;
static int  match=0;
static int  beep=1;
static int  startbrowser=0;
static char *browser=NULL;

/* ========================================================================== */
/*                            ReadContext                                     */
/* -------------------------------------------------------------------------- */
/*  ReadContext() reads the Hermes defaults file and sets the appropriate
 *  parameters.
 */
extern void ReadContext(TaskStatus template)
{
   char filename[128];
   char *homedef, *dirdef;
   int  size;

   dirdef = FileContents(DEFFILE,&size);
   strcpy(filename,getenv("HOME"));
   strcat(filename,"/.gipsy/");
   strcat(filename,DEFFILE);
   homedef = FileContents(filename,&size);
   ProcessFile(homedef, template);
   ProcessFile(dirdef, template);
}

/* ========================================================================== */
/*                               GetTaskDir                                   */
/* -------------------------------------------------------------------------- */
/*  Obtain the working directory string from the template task status block.
 */
extern char *GetTaskDir(void)
{
   extern TaskStatus Template;
   return Template->workdir;
}

/* ========================================================================== */
/*                               SetTaskDir                                   */
/* -------------------------------------------------------------------------- */
/*  Set the working directory string in the template task status block.
 */
extern int SetTaskDir(char *newdir)
{
   extern TaskStatus Template;
   char curdir[128], anewdir[128];

   if (!newdir) return 0;                          /* user cancelled prompter */
   (void)getcwd(curdir, 128);
   (void)chdir(Template->workdir);
   if (!chdir(newdir)) {
      strcpy(Template->workdir,getcwd(anewdir, 128));
      (void)chdir(curdir);
      return 0;
   } else return -1;
}

/* ========================================================================== */
/*                              MatchOn                                       */
/* -------------------------------------------------------------------------- */
/*  MatchOn returns 1 or 0 depending on whether minimal matching has been
 *  specified or not.
 */
extern int MatchOn(void)
{
   return match;
}
/* ========================================================================== */
/*                               BeepOn                                       */
/* -------------------------------------------------------------------------- */
/*  BeepOn returns 1 or 0 depending on whether the terminal beep has been
 *  switched on or not.
 */
extern int BeepOn(void)
{
   return beep;
}
/* ========================================================================== */
/*                                 WebBrowser                                 */
/* -------------------------------------------------------------------------- */
extern char *WebBrowser(void)
{
   return browser;
}

/* ========================================================================== */
/*                                 StartBrowser                               */
/* -------------------------------------------------------------------------- */
extern int StartBrowser(void)
{
   return startbrowser;
}

/* -------------------------------------------------------------------------- */
/*                            ProcessFile                                     */
/* -------------------------------------------------------------------------- */
/*  ProcessFile() processes an in-memory image of a Hermes defaults file and
 *  sets the appropriate parameters.
 */
static void ProcessFile(char* defaults, TaskStatus template)
{
   enum          { ERRORLEVEL,   MESSAGELEVEL,   OUTPUTMODE,
                   TASKPATH,     DIRECTORY,      HIDE,
                   COA   ,       LOG    ,        PRINTER,
                   TEKDEVICE,    MATCH,          KEYHELP,
                   BEEP,         NTASKS,         WEBBROWSER,
                   STARTBROWSER } i;
   char *names[]={"ERRORLEVEL=","MESSAGELEVEL=","OUTPUTMODE=",
                  "TASKPATH=",  "DIRECTORY=",   "HIDE=",
                  "SCREEN=",    "LOGFILE=",     "PRINTER=",
                  "TEKDEVICE=", "MATCH=",       "KEYHELP=", 
                  "BEEP=",      "NTASK=",       "WEB_BROWSER=",
                  "STARTBROWSER=", NULL};
   char *start;
   char *key, *value;
   char message[80];
   int  pstatus;
   long cmdid;
   int  ivalue;

   if (defaults) {
      for (start=defaults; *start==' '||*start=='\t'||*start=='\n'; start++){;}
      pstatus = ParseCmd(start);
      free(defaults);
      if (pstatus && pstatus!=NOTSKNAME) {
         strcpy(message,"Syntax error: ");
         strcat(message,CmdErrString(0));
         ReportError(message);
         ReportError(NULL);
         return;
      }
      cmdid = CmdGetInfo();
      for (;;) {
         CmdParam(cmdid,&key,&value);
         if (!key) break;
         for (i=0; names[i]; i++) if (!strcmp(key,names[i])) break;
         switch (i) {
            case  ERRORLEVEL   :
                  ivalue = atoi(value);
                  if (ivalue<1 || ivalue>4) {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  } else template->errlev = ivalue;
                  break;

            case  MESSAGELEVEL :
                  ivalue = atoi(value);
                  if (ivalue<1 || ivalue>4)  {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  else
                     template->meslev = ivalue;
                  break;

            case  OUTPUTMODE   :
                  StringUp(value);
                  if      (!strcmp(value,"EXPERT"))
                     template->output_mode = EXPERIENCED;
                  else if (!strcmp(value,"NORMAL"))
                     template->output_mode = NORMAL;
                  else if (!strcmp(value,"TEST"))
                     template->output_mode = TEST;
                  else {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;

            case  TASKPATH     :
                  if (SetExecPath(value)) {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;

            case  DIRECTORY    :
                  if (SetTaskDir(value)) {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;

            case  HIDE         :
                  StringUp(value);
                  if      (!strcmp(value,"ON"))  template->unhide = 0;
                  else if (!strcmp(value,"OFF")) template->unhide = 1;
                  else {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;

            case  COA          :
                  StringUp(value);
                  if      (!strcmp(value,"ON"))  template->devmask |=  SCREEN;
                  else if (!strcmp(value,"OFF")) template->devmask &= ~SCREEN;
                  else {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;

            case  LOG          :
                  StringUp(value);
                  if      (!strcmp(value,"ON"))  template->devmask |=  LOGFILE;
                  else if (!strcmp(value,"OFF")) template->devmask &= ~LOGFILE;
                  else {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;

            case  PRINTER      :
                  ivalue = atoi(value);
                  if (SetPrinter(ivalue)) {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;

            case TEKDEVICE     :
                  StringUp(value);
                  if (SelectTek(value)) {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;

            case  MATCH        :
                  StringUp(value);
                  if      (!strcmp(value,"ON"))  match = 1;
                  else if (!strcmp(value,"OFF")) /* no-op */ ;
                  else {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;

            case  BEEP        :
                  StringUp(value);
                  if      (!strcmp(value,"ON"))  beep = 1;
                  else if (!strcmp(value,"OFF")) beep = 0;
                  else {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;

            case  KEYHELP      :
                  StringUp(value);
                  if      (!strcmp(value,"ON"))  SetKeyHelp(1);
                  else if (!strcmp(value,"OFF")) SetKeyHelp(0);
                  else {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;

            case  NTASKS       :
                  ivalue = atoi(value);
                  if (ivalue<1) {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  } else NTASK = ivalue;
                  break;
                  
            case  WEBBROWSER   :
                  if (*value) browser = StrDup(value);
                  break;
                  
            case  STARTBROWSER :
                  StringUp(value);
                  if      (!strcmp(value,"ON"))  startbrowser = 1;
                  else if (!strcmp(value,"OFF")) startbrowser = 0;
                  else {
                     strcpy(message,key); strcat(message,value);
                     ReportError(message);
                  }
                  break;


            default:
                  strcpy(message,"Unknown option: ");
                  strcat(message,key);
                  ReportError(message);
         }
      }
      CmdDelete(cmdid);
      if (errors) ReportError(NULL);
   }
}

/* -------------------------------------------------------------------------- */
/*                            ReportError                                     */
/* -------------------------------------------------------------------------- */
static void ReportError(char *message)
{
   if (!errors) printf("\n\nError(s) in Hermes default file:\n\n");
   if (message) {
      printf("   %s\n",message);
   } else {
      printf("\n\n");
      sleep(5);
   }
   errors++;
}

#<

#>            search.c
/* search.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It is responsible for searching a string
in the COA.   Author: J.P. Terlouw
*/

#include "stddef.h"
#include "string.h"
#include "alloc.h"
#include "prompter.h"
#include "coamgr.h"
#include "keysym.h"
#include "search.h"

static long prid=0;
static int sdir;
static char *seastr=NULL;

extern int LINES, COLS;
static int dosearch(long id, int status, char *result, void *arg);
static char wwwhelp[]="*hermes/COA.html#search";

/* ========================================================================== */
/*                                  Search                                    */
/* -------------------------------------------------------------------------- */
extern void Search(int direction)
{
   sdir = direction;
   if (prid) {
      PrompterType(prid,CR);
   } else {
      prid = Prompter("Enter search string:",seastr,dosearch,NULL,LINES-8,18);
      SetPrompterHelp(prid,wwwhelp);
   }
}

/* -------------------------------------------------------------------------- */
/*                                 dosearch                                   */
/* -------------------------------------------------------------------------- */
static int dosearch(long id, int status, char *result, void *arg)
{
   int searchstat;

   if (status==PROMPT_CANCEL) {
      prid = 0;
      return 0;
   }
   if (seastr) Free(seastr);
   seastr = Malloc(strlen(result)+1);
   strcpy(seastr,result);
   if (sdir>0) SetPrompterActive(id,"  >>> searching >>>");
   else        SetPrompterActive(id,"  <<< searching <<<");
   searchstat = SearchCoa(seastr,sdir);
   switch (searchstat) {
      case -1: {
         SetPrompterError(id, " - not found.");
         break;
      }

      case CTRL_R: {
         SetPrompterError(id," ");
         sdir = -1; break;
      }

      case CTRL_S: {
         SetPrompterError(id," ");
         sdir = +1; break;
      }

      case 0:
      case CTRL_C: {
         prid = 0;
         return 0;
      }

      default: {
         SetPrompterError(id,"  - interrupted.");
      }
   }
   return -1;
}
#<

#>            screen.c
/* screen.c
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands


Windows package for tHermes. It has been built on top of curses(3) of which
only the base window is used; the windows implemented by this module are
not related to the windows provided by curses.

Author: J.P. Terlouw.
*/

#if     defined(__convex__) && defined(__GNUC__)
#define _SYS_TERMIOS_H_
#endif

#if	defined(__linux__) | defined(__APPLE__)
#define	speed_t	unsigned int
#endif
#include "stddef.h"
#include "stdarg.h"
#ifdef	__aix__
#ifndef	_TERM_H
#define	_TERM_H
#endif
#endif
#ifdef	__GIPSY_CURSESX__
#include <cursesX.h>
#else
#include <curses.h>
#endif
#include "alloc.h"
#include "windows.h"

static Window m_windows=NULL, u_windows=NULL;  /* mapped and unmapped windows */
static int cursorx, cursory;
static int active=0;       /* active flag for suspend and resume              */
static int bell=1;         /* indicates whether bell is on                    */

static void Expose(Window);
static void InvFrame(Window);

/* ========================================================================== */
/*                                  InitWin                                   */
/* -------------------------------------------------------------------------- */
/* InitWin initializes the windows package.
 */
extern void InitWin(void)
{
   active = 1;
   initscr();        /* initialize curses               */
   raw();            /* declare terminal "raw" I/O mode */
}

/* ========================================================================== */
/*                                  FinisWin                                  */
/* -------------------------------------------------------------------------- */
/* FinisWin terminates the windows package.
 */
extern void FinisWin(void)
{
   Window win, next;

   for (win=m_windows; win; win=next) {
      next = win->next;
      DeleteWindow(win);      /* mapped */
   }
   for (win=u_windows; win; win=next) {
      next = win->next;
      DeleteWindow(win);    /* unmapped */
   }
   clear();                                      /* clear curses base window */
   refresh();                                    /* show the change          */
   endwin();                                     /* terminate curses         */
   active = 0;
}

/* ========================================================================== */
/*                                 CreateWindow                               */
/* -------------------------------------------------------------------------- */
/* CreateWindow creates a window with ny rows and nx columns.
 * This window is initially not mapped to the terminal screen.
 */
extern Window CreateWindow(int ny, int nx)
{
   Window win;
   int    nc;
   char   *c;

   win = New(_Window);                              /*  create window struct */
   win->next = u_windows;                           /* link in unmapped list */
   u_windows = win;
   nc = nx*ny;
   win->backup = (char*)Malloc(nc);                 /* create backup store   */
   win->private = TRUE;
   win->nx = nx; win->ny = ny;
   win->x = 0; win->y = 0;                          /* initialize position   */
   win->mapped = FALSE;
   win->framed = FALSE;
   c = win->backup;
   while (nc--) *(c++) = ' ';                       /* clear backup store    */
   return win;
}

/* ========================================================================== */
/*                                 DeleteWindow                               */
/* -------------------------------------------------------------------------- */
/* DeleteWindow deletes a window. If necessary, it is unmapped first.
 */
extern void DeleteWindow(Window win)
{
   Window *winp;

   if (win->mapped) {
      UnmapWindow(win);
   }
   winp = &u_windows;
   while (*winp) {
      if (*winp==win) {
         *winp = win->next;                    /* remove from linked list     */
         if (win->private) Free(win->backup);  /* delete private backup store */
         Free(win);                            /* delete window struct        */
         break;
      } else winp = &(*winp)->next;
   }
}

/* ========================================================================== */
/*                                 UnmapWindow                                */
/* -------------------------------------------------------------------------- */
/* UnmapWindow unmaps a window, i.e. it removes it from the terminal screen.
 */
extern void UnmapWindow(Window win)
{
   Window *winp=&m_windows;

   if (!win->mapped) return;             /* already unmapped? do nothing      */
   while (*winp) {
      if (*winp==win) {
         *winp = win->next;             /* move from mapped- to unmapped list */
         win->next = u_windows;
         u_windows = win;
         win->mapped = FALSE;
         Expose(win);                   /* reveal windows which were covered  */
         break;
      } else winp = &(*winp)->next;
   }
}

/* ========================================================================== */
/*                                  MapWindow                                 */
/* -------------------------------------------------------------------------- */
/* MapWindow maps a window, i.e. it is brought to the terminal screen.
 * Position (0,0) of the window will correspond to position (y,x) on the
 * screen. If either y or x is negative, the window will be mapped to the
 * position specified in the window structure.
 */
extern void MapWindow(Window win, int y, int x)
{
   Window *winp=&u_windows;

   if (win->mapped) {
      if (x==-1 && y==-1 && win==m_windows) {
         RefreshWindow(win);            /* mapped window in correct position  */
         return;
      } else {
         Window *winp=&m_windows;
         while (*winp) {
            if (*winp==win) {
               *winp = win->next;             /* move to front of mapped list */
               win->next = m_windows;
               m_windows = win;
               break;
            } else winp = &(*winp)->next;
         }
         RedrawScreen();
         return;
      }
   }

   while (*winp) {
      if (*winp==win) {
         *winp = win->next;             /* move from unmapped- to mapped list */
         win->next = m_windows;
         m_windows = win;
         if (x >=0 && y >=0) {
            win->x = x;                 /* set mapping position */
            win->y = y;
         }
         win->mapped = TRUE;
         RedrawWindow(win);
      } else winp = &(*winp)->next;
   }
   /* full size window: assume client wants to monopolize screen */
}

/* ========================================================================== */
/*                                   PutChar                                  */
/* -------------------------------------------------------------------------- */
/* PutChar puts a character in a specified position of a window.
 * The character will not be visible immediately on the screen, even if
 * the window is mapped. To make changes visible, RefreshWindow must be
 * called.
 */
extern void PutChar(Window win, int y, int x, char c)
{
   int xabs, yabs;
   Window current;

   if (y<win->ny && x<win->nx && y>=0 && x>=0) {
      *(win->backup + win->nx*y + x) = c;
      if (win->mapped && active) {
         xabs = win->x+x;
         yabs = win->y+y;
         /*
          * descend from highest window to the target window and check whether
          * the character position is covered by higher windows.
          */
         for (current=m_windows; current; current=current->next) {
            if (current==win) {
               mvaddch(yabs,xabs,c);
               break;
            }
            if (xabs >= current->x && yabs >= current->y &&
                xabs < current->x+current->nx && yabs < current->y+current->ny)
                break;

         }
      }
   }
}

/* ========================================================================== */
/*                                RefreshWindow                               */
/* -------------------------------------------------------------------------- */
/*  RefreshWindow makes changes made to a window visible.
 */
extern void RefreshWindow(Window win)
{
   if (active) {
      InvFrame(win);
      move(cursory,cursorx);
      refresh();
   }
}

/* ========================================================================== */
/*                                    Expose                                  */
/* -------------------------------------------------------------------------- */
/*  Expose re-draws everything which has been covered by the specified window.
 */
static void Expose(Window win)
{
/*
 * Brute force method used: refresh all windows completely.
 * Could be changed later.
 */
   RedrawScreen();
}

/* ========================================================================== */
/*                                InvFrame                                    */
/* -------------------------------------------------------------------------- */
/*  InvFrame inverts the frame if the window is 'framed'.
 */
static void InvFrame(Window win)
{
   char *cptr=win->backup;
   int  row, col;

   if (win->framed && active) {
      standout();
      cptr = win->backup;
      for (col=0; col<win->nx; col++) {
         PutChar(win,0,col,*(cptr+col));
         PutChar(win,win->ny-1,col,*(cptr+win->nx*(win->ny-1)+col));
      }
      for (row=1; row<win->ny-1; row++) {
         PutChar(win,row,0,*(cptr+row*win->nx));
         PutChar(win,row,win->nx-1,*(cptr+row*win->nx+win->nx-1));
      }
      standend();
   }
}


/* ========================================================================== */
/*                               ClearWindow                                  */
/* -------------------------------------------------------------------------- */
/* ClearWindow clears a window to blanks.
 */
extern void ClearWindow(Window win)
{
   int row, col;

   for (row=0; row<win->ny; row++)
      for (col=0; col<win->nx; col++)
         PutChar(win,row,col,' ');
   InvFrame(win);
}

/* ========================================================================== */
/*                              PutString                                     */
/* -------------------------------------------------------------------------- */
/* PutString puts a zero-terminated string in a window, starting at
 * position (x,y). Only printable characters are allowed in the string.
 */
extern void PutString(Window win, int y, int x, char* string)
{
   int maxc = win->nx;

   while (maxc-- && *string) PutChar(win, y, x++, *(string++));
   /* this can be refined, e.g. to allow newlines etc. */
}

/* ========================================================================== */
/*                               FrameWindow                                  */
/* -------------------------------------------------------------------------- */
/* FrameWindow draws declares the window 'framed' and then actually inverts
 * the window border.
 */
extern void FrameWindow(Window win)
{
   win->framed = TRUE;
   InvFrame(win);
}

/* ========================================================================== */
/*                                 PutLine                                    */
/* -------------------------------------------------------------------------- */
/* PutLine puts a zero-terminated string on row 'y' of a window.
 */
extern void PutLine(Window win, int y, char *string)
{
   int start=0, end=win->nx, top=0, bottom=win->ny;
   int col;

   if (win->framed) {
      start++;
      end--;
      top++;
      bottom--;
   }

   if (y>=top && y<bottom) {
      for(col=start; col<end; col++)
      if (*string) PutChar(win,y,col,*(string++)); else PutChar(win,y,col,' ');
   }
}

/* ========================================================================== */
/*                                PutCursor                                   */
/* -------------------------------------------------------------------------- */
/* PutCursor sets the cursor position on position (x,y) inside a window.
 */
extern void PutCursor(Window win, int y, int x)
{
   cursorx = win->x+x;
   cursory = win->y+y;
}

/* ========================================================================== */
/*                               RedrawWindow                                 */
/* -------------------------------------------------------------------------- */
/* RedrawWindow redraws a window.
 */
extern void RedrawWindow(Window win)
{
   char *cptr=win->backup;
   int row, col;

   for (row=0; row<win->ny; row++)
      for (col=0; col<win->nx; col++)
         PutChar(win,row,col,*(cptr++));
   RefreshWindow(win);
}

/* ========================================================================== */
/*                                  RedrawScreen                              */
/* -------------------------------------------------------------------------- */
/* RedrawScreen redraws all windows.
 */
extern void RedrawScreen(void)
{
   Window win;

   for (win=m_windows; win; win=win->next) RedrawWindow(win);
}

/* ========================================================================== */
/*                             RepairScreen                                   */
/* -------------------------------------------------------------------------- */
/* RepairScreen rewrites all characters on the screen.
 */
extern void RepairScreen(void)
{
   if (active) {
      touchwin(curscr);
      wrefresh(curscr);
   }
}

/* ========================================================================== */
/*                             ScrollWindow                                   */
/* -------------------------------------------------------------------------- */
/* ScrollWindow scrolls a window up a specified number of lines.
 */
extern void ScrollWindow(Window win, int nlines)
{
   int xstart=0, xend=win->nx, ystart=0, yend=win->ny;
   int row, col;

   if (nlines>0) {
      if (win->framed) {
         xstart++; xend--; ystart++; yend--;
      }
      for (row=ystart; row<yend-nlines; row++) {
         for (col=xstart; col<xend; col++) {
            PutChar(win,row,col,win->backup[(row+nlines)*win->nx+col]);
         }
      }
      for (row=yend-nlines; row<yend; row++) {
         for (col=xstart; col<xend; col++) {
            PutChar(win,row,col,' ');
         }
      }
   }
}
/* ========================================================================== */
/*                            SuspendScreen                                   */
/* -------------------------------------------------------------------------- */
/*  SuspendScreen clears the screen and clears the active flag, causing all
 *  terminal output to be blocked.
 */
extern void SuspendScreen(void)
{
   if (active) {
      clear();
      refresh();
      endwin();
      active = 0;
   }
}
/* ========================================================================== */
/*                            ResumeScreen                                    */
/* -------------------------------------------------------------------------- */
/*  ResumeScreen sets the active flag and redraws the terminal screen.
 */
extern void ResumeScreen(void)
{
   if (!active) {
      active = 1;
      initscr();
      raw();
      RedrawScreen();
   }
}

/* ========================================================================== */
/*                                 Beep                                       */
/* -------------------------------------------------------------------------- */
/*
 *  Produce an audible beep.
 */
extern void Beep(void)
{
   size_t write(int, const void *, size_t);

   if (bell && active) write(1,"\007",1);
}

/* ========================================================================== */
/*                                 SwitchBeep                                 */
/* -------------------------------------------------------------------------- */
/*
 *  Toggle bell.
 */
extern void SwitchBeep(void)
{
   bell = !bell;
}

#<

#>            showexpr.c
/* showexpr.c
                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It handles the tHermes expressions display
Author: J.P. Terlouw
*/

#include "stddef.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "stdio.h"
#include "dcdparse.h"
#include "thermescom.h"
#include "showexpr.h"

extern int LINES, COLS;                      /* screen dimensions from Curses */

static Window win=NULL;
static long pid;                             /* expression parser callback id */
static int xsize, ysize;

static void exprnotify(long, ExprVal*, void*);

/* ========================================================================== */
/*                                 ShowExpr                                   */
/* -------------------------------------------------------------------------- */
/*
 *  ShowExpr pops up a transient window which will display the most recently
 *  evaluated numerical expression and its value.
 */
extern void ShowExpr(void)
{
   int i;
   char topline[]=" Expression Display ";

   if (!win) {
      xsize=COLS;
      ysize=4;
      win = CreateWindow(ysize,xsize);
      for (i=0; i<win->nx; i++) {
         PutChar(win, 0, i, '=');
         PutChar(win, win->ny-1, i, '=');
      }
      PutString(win, 0, (win->nx-strlen(topline))/2, topline);
      MapWindow(win,0,0);
      pid = InsertExprProc(exprnotify,NULL);     /* expression parse callback */
   } else {
      RemoveExprProc(pid);
      DeleteWindow(win);
      win = NULL;
   }
}

/* -------------------------------------------------------------------------- */
/*                             exprnotify                                     */
/* -------------------------------------------------------------------------- */
/*   expression evaluate handler
 */
static void exprnotify(long id, ExprVal *exprstate, void *arg)
{
   PutLine(win, 1, exprstate->expr);
   PutLine(win, 2, exprstate->value);
   RefreshWindow(win); 
}
#<

#>            showkeys.c
/* showkeys.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It handles the tHermes task keywords display
Author: J.P. Terlouw
*/

#include "stddef.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "stdio.h"
#include "thermescom.h"
#include "prompter.h"
#include "parmgr.h"
#include "showkeys.h"

#define OVERLAP 2                            /* number of lines of overlap    */
#define KEYPHASE 0
#define FILEPHASE 1
#define RESIZE  win->ny = nlines>ysize?ysize:nlines;

extern int NTASK;
extern int LINES, COLS;                      /* screen dimensions from Curses */

static Window win=NULL, top=NULL;
static char *text;
static char current[NAMLEN+1];
static int nlines, startline;
static long pid;                              /* parameter change callback id */
static long kid;                              /* keyboard callback id         */
static int xsize, ysize;
static int phase=KEYPHASE;

static void deactivate(void);
static int  getchars(long,int,void*);
static void parnotify(long,char*,void*);
static void updatewin(void);
static int  writefile(long,int,char*,void*);
static int  writercl(long,int,char*,void*);
static char wwwhelp[]="*hermes/keytable.html#saving";

/* ========================================================================== */
/*                                 ShowKeys                                   */
/* -------------------------------------------------------------------------- */
/*
 *  ShowKeys pops up a transient window containing the current set of
 *  keyword parameters of the indicated task.
 */
extern void ShowKeys(char *taskname)
{
   int i;
   char topline[40];

   xsize=COLS;
   ysize=LINES-NUCA-NTASK-1;
   if (taskname && *taskname) {
      strcpy(current,taskname);
      for (i=0; i<NAMLEN; i++) current[i] = toupper(current[i]);
      text = display_par(current,xsize);          /* obtain parameter "image" */
      if (!win) {
         top = CreateWindow(1,xsize);          /* header line                 */
         win = CreateWindow(1,xsize);          /* keywords and values         */
         free(win->backup);                    /* maintain window backup...   */
         win->private = FALSE;                 /* ... outside screen package  */
      }
      nlines = strlen(text)/xsize;
      RESIZE                                  /* resize window */
      win->backup = text;
      for (i=0; i<xsize; i++) PutChar(top,0,i,'=');
      sprintf(topline," Keywords for: %s ",current);
      PutString(top,0,(xsize-strlen(topline))/2,topline);
      MapWindow(top,0,0);
      MapWindow(win,1,0);
      startline = 0;
      kid = InsertKeyboardProc(getchars,NULL);/* install keyboard handler... */
      pid = InsertParProc(parnotify,NULL);   /* and parameter change handler */
   } else {
      Beep();
   }
}

/* -------------------------------------------------------------------------- */
/*                              getchars                                      */
/* -------------------------------------------------------------------------- */
/*   keyboard handler
 */
static int getchars(long id, int c, void *arg)
{
   if (c==('K'|ESCBIT)) {                                 /* "Quit" command */
      deactivate();
      return 0;
   }

   if (c==('W'|ESCBIT)) {                            /* "Write file" command */
      char deffile[NAMLEN+4+1];
      strcpy(deffile,current);
      StringLow(deffile);
      strcat(deffile,".def");
      SetPrompterHelp(
         SetPrompterError(
            Prompter("Enter file name to receive parameters:", deffile,
            writefile,NULL,  win->ny/2, 20),
            "         Error: file cannot be written"),
         wwwhelp
      );
         ;
      return 0;
   }

   if (c==('E'|ESCBIT)) {
      EditKeys(current);
   }

   if (c==('R'|ESCBIT)) {
      long prid;
      phase = KEYPHASE;
      prid = Prompter("Keyword to store:   ", NULL, writercl, NULL, win->ny, 0);
      (void)SetPrompterError(prid, "  Error: not present");
      return 0;
   }
   if (win->ny>=ysize) {
      switch (c) {
         case CTRL_Z:  startline = startline - ysize + OVERLAP; break;

         case CTRL_V:  startline = startline + ysize - OVERLAP; break;

         case CTRL_R:
         case CTRL_S:                            /* disallow COA searching */
         case CTRL_P:  Beep(); return 0;         /* disallow COA ^P action */

         default:      return c;                 /* propagate              */
      }
      updatewin();
      return 0;
   } else return c;
}

/* -------------------------------------------------------------------------- */
/*                              parnotify                                     */
/* -------------------------------------------------------------------------- */
/*   parameter change handler
 */
static void parnotify(long id, char *taskname, void *arg)
{
   if (!strcmp(taskname,current)) {
      free(text);                           /* release old image */
      text = display_par(current,xsize);    /* obtain new image  */
      nlines = strlen(text)/xsize;
      RESIZE                                /* resize */
      updatewin();
   }
}

/* -------------------------------------------------------------------------- */
/*                                updatewin                                   */
/* -------------------------------------------------------------------------- */
static void updatewin(void)
{
   if (startline>(nlines-ysize)) startline = nlines-ysize;
   if (startline<0) startline = 0;            /* force startline within range */
   win->backup = text+startline*xsize;        /* connect to window */
   RedrawScreen();                /*  *Screen* because window may have shrunk */
}

/* -------------------------------------------------------------------------- */
/*                                writefile                                   */
/* -------------------------------------------------------------------------- */
static int writefile(long id, int status, char *filename, void *arg)
{
   if (status==PROMPT_CANCEL) return 0;            /* user cancelled prompter */
   return write_par(current,filename);
}

/* -------------------------------------------------------------------------- */
/*                               writercl                                     */
/* -------------------------------------------------------------------------- */
static int writercl(long id, int status, char *name, void *arg)
{
   static char key[KEYLEN+1];

   if (status==PROMPT_CANCEL) return 0;    /* user cancelled prompter         */
   if (!text) return 0;                    /* fake success to remove prompter */
   switch (phase) {
      case KEYPHASE: strcpy(key,name);
                     StringUp(key);
                     if (key[strlen(key)-1]!='=') strcat(key,"=");
                     if (!write_rcl(current,key,NULL)) {
                        phase = FILEPHASE;
                        (void)Prompter("Filename:                 ", NULL,
                                        writercl, NULL, win->ny, 0);
                        return 0;
                     } else return -1;
      case FILEPHASE: if (!write_rcl(current,key,name)) return 0;
                      else {
                         SetPrompterError(id, "Error: cannot be written");
                         return -1;
                      }
   }
}

/* -------------------------------------------------------------------------- */
/*                                deactivate                                  */
/* -------------------------------------------------------------------------- */
static void deactivate(void)
{
   UnmapWindow(win);
   UnmapWindow(top);
   RemoveKeyboardProc(kid);
   RemoveParProc(pid);
   Delete(text);
}
#<

#>            socketpair.c
/*  socketpair.c
 *
 *  Equivalent of the BSD socketpair system call.
 *  It is used for Alliant because Concentrix issues two irrelevant warning
 *  messages on the system console for every call made
 *  and for systems which do not know the socketpair system call (System V).
 */
#include	"stddef.h"
#include	"stdio.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define FAILURE(x) {perror(x); return -1;}
int socketpair(int domain, int type, int protocol, int sv[2])
{
   struct sockaddr_un name;
   int                size;
   int                s;

   if ((s = socket(domain,type,protocol))<0) FAILURE("s0");
   if ((sv[1] = socket(domain,type,protocol))<0) FAILURE("s1");
   name.sun_family = AF_UNIX;
   strcpy(name.sun_path,"SP.TMP");
   if (bind(s,(struct sockaddr *)&name,sizeof(name))) FAILURE("b");
   if (listen(s,1)) FAILURE("l");
   if (connect(sv[1],(struct sockaddr *)&name,sizeof(name))) FAILURE("c");
   size = sizeof(name);
   if ((sv[0] = accept(s,(struct sockaddr *)&name,&size))<0) FAILURE("a");
   close(s);
   remove("SP.TMP");
   return 0;
}
#<

#>            stubs.c
/* stubs.c
                              COPYRIGHT (c) 1993
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

*/

#include "gipsyc.h"
#include "stdio.h"
extern void error_c( fint *level, fchar message)
{
   /* should not be called */
   printf("ERROR called!\n");
}

extern bool gipsytask_c(void)
{
   return FALSE;
}

extern int decodeint_c(fchar expr, fint *out, fint *num)
{
   return -1;
}
extern int decodereal_c(fchar expr, float *out, fint *num)
{
   return -1;
}
extern int decodedble_c(fchar expr, double *out, fint *num)
{
   return -1;
}

#if defined(__sun__)&&defined(__bsd__)
#include <sys/param.h>
extern char *getcwd(char *buf, int size)
{
   return getwd(buf);
}
#endif
#<

#>            substi.c
/*  substi.c
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands


           'substi.dc3'
Module:     substi

Purpose:    Collection of routines handling keyword substitution.

Category:   SYSTEM

File:       substi.c

Description: The routines in this module are exclusively used by Hermes
             to provide keyword substitution for deputy tasks.
             The following functions exist in this module:
             sbinit, sbspec, sbback, sbstte, rev_sbstte.

Author:     J.P. Terlouw

See also:   subst.dc2 deputy.dc2

Updates:    Dec  2, 1990: JPT, document created.
            Jan 17, 1991: JPT, revised.

*/

#include "hermescom.h"
#include "string.h"

#define MAXDEP 10                    /* maximum depth of reverse substitution */

static KeySub freelist;

/*
           'sbinit.dc3'
Function:   sbinit

Purpose:    Initialize keyword substitution free list.

Category:   SYSTEM

File:       substi.c

Author:     J.P. Terlouw

Use:        sbinit(nsub);

              int nsub :  number of substitution structures to be allocated.
                          If necessary this number is increased later
                          automatically.

            This function is used exclusively by Hermes.

Updates:    Dec  2, 1990: JPT, document created.
            Jan 17, 1991: JPT, revised.

*/

void sbinit(int nsub)
{
   KeySub cursub;

   freelist = (KeySub)Calloc(nsub,sizeof(struct _KeySub));
   cursub   = freelist;
   while(--nsub) {
      cursub->next = cursub+1;
      cursub++;
   }
}

/*
           'sbback.dc3'
Function:   sbback

Purpose:    Reclaim keyword substitutions from the current process of a task.

Category:   SYSTEM

File:       substi.c

Author:     J.P. Terlouw

Use:        sbback(task);

                  TaskStatus task : indicates the task for which the function
                                    is performed.

            This function is used exclusively by Hermes, before the current
            process block of a task (either the task 'root' process or a deputy)
            which has just exited, is removed.

Updates:    Dec  2, 1990: JPT, document created.
            Jan 17, 1991: JPT, revised.

*/
void sbback(TaskStatus task)
{
   KeySub current, next;

   current  = task->proc->keysub;
   while (current) {
      next     = current->next;
      current->next = freelist;
      freelist = current;
      current  = next;
   }
   task->proc->keysub = NULL;       /* +++ defensive, remove after testing? */
}

/*
           'sbspec.dc3'
Function:   sbspec

Purpose:    Specify keyword substitution.

Category:   SYSTEM

File:       substi.c

Author:     J.P. Terlouw

Use:        result = sbspec(task, key, equ);

                   int result: 0 successful call;  -1 error.
                               If sbspec is used correctly by Hermes, the error
                               only occurs when the internal table is full.
                   TaskStatus task :  indicates the task for which the function
                                      is performed.
                   char*  key: containing the keyword
                   char*  equ: containing the equivalent keyword

            This function is used exclusively by Hermes.

Updates:    Dec  2, 1990: JPT, document created.
            Jan 17, 1991: JPT, revised.

*/
int sbspec(TaskStatus task, char *key, char *equ)
{
   KeySub new;

   if (freelist) {
      new = freelist;         /* obtain substitution block from the free list */
      freelist = new->next;
   } else new = (KeySub)Calloc(1,sizeof(struct _KeySub));

   new->next = task->proc->keysub;         /* link to process status block */
   task->proc->keysub = new;
   strncpy(new->key,key,KEYLEN);                   /* fill the block */
   strncpy(new->equ,equ,KEYLEN);
   return 0;
}

/*
           'sbstte.dc3'
Function:   sbstte

Purpose:    Perform keyword substitution.

Category:   SYSTEM

File:       substi.c

Author:     J.P. Terlouw

Use:        sbstte(task, key);

                   TaskStatus task :  indicates the task for which the function
                                      is performed.
                   char*  key: containing the keyword and receiving the
                               equivalent keyword.

            This function is used exclusively by Hermes.

Updates:    Dec  2, 1990: JPT, document created.
            Jan 17, 1991: JPT, revised.

*/
void sbstte(TaskStatus task, char *key)
{
   KeySub     cursub;
   ProcStatus proc=(ProcStatus)(task->proc->next);

   while (proc) {
      cursub = proc->keysub;
      while (cursub) {
         if (strcmp(key,cursub->key)==0) {
            strcpy(key,cursub->equ);
            break;
         }
         cursub = cursub->next;
      }
      proc   = (ProcStatus)(proc->next);
   }
}

/*+++ Get rid of rev_sbstte? It is not used now. */
/*
           'rev_sbstte.dc3'
Function:   rev_sbstte

Purpose:    Perform reverse keyword substitution.

Category:   SYSTEM

File:       substi.c

Author:     J.P. Terlouw

Use:        rev_sbstte(task, key);

                   TaskStatus task :  indicates the task for which the function
                                      is performed.
                   char*  key: containing the equivalent keyword and receiving
                               the standard keyword.

            This routine is used by Hermes to translate the prompted keyword
            to the keyword as defined in a deputy task for the purpose of
            finding that keyword as part of the context sensitive help
            facility.

Updates:    Dec  8, 1990: JPT, document created.
            Jan 17, 1991: JPT, revised.

*/
void rev_sbstte(TaskStatus task, char *key)
{
   KeySub     cursub;
   ProcStatus curproc=task->proc;
   ProcStatus proc[MAXDEP];
   int        nproc;

/*
 * First store process block pointers of current task because they must
 * be accessed in reverse order.
 */
   for(nproc=0; nproc<MAXDEP; nproc++) {
      if (!curproc->next) break;
      proc[nproc] = (ProcStatus)curproc->next;
      curproc = (ProcStatus)(curproc->next);
   }

/*
 * Try to obtain the reverse substitution. In special cases the result may be
 * incorrect, but no checks are made because an incorrect result does not
 * do any harm.
 */
   while (nproc) {
      cursub = proc[--nproc]->keysub;
      while (cursub) {
         if (strcmp(key,cursub->equ)==0) {
            strcpy(key,cursub->key);
            break;
         }
         cursub = cursub->next;
      }
   }
}
#<

#>            taskmgr.c
/* taskmgr.c
                              COPYRIGHT (c) 1990
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands


Module:      taskmgr

Purpose:     Private to hermes. Collection of routines dealing with
             servant tasks and their status blocks.

File:        taskmgr.c

Author:      J.P. Terlouw

Description: There are six external routines:
             inittask       :  initialize the module;
             starttask      :  start a new task or deputy;
             rundown        :  clean up an exited task.
             InsertTaskProc : register a task status change callback
             RemoveTaskProc : remove a task status change callback registration
             ExitChan       : define file descriptor to report process exits

Author:      J.P. Terlouw

Updates:     Oct 18, 1990, JPT: created document.
             Jan 17, 1991, JPT: revised.
             Aug  3, 1992, JPT: added callback facility
             Oct  8, 1992, JPT: added ExitChan
             Aug 27, 2007, JPT: childsigh: handle multiple process terminations


*/

#include	"stddef.h"
#include	"stdlib.h"
#include	"string.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#if defined(__aix__) | defined(__alpha__)
#define _BSD
#endif
#if ( defined(__linux__) | defined(__APPLE__)) && !defined(__USE_BSD)
#define __USE_BSD
#endif
#if defined(__sgi__) && !defined(_BSD_COMPAT)
#define	_BSD_COMPAT
#endif
#include <sys/wait.h>
#if defined(__sgi__)
#undef	_BSD_COMPAT
#endif
#include <fcntl.h>

#include "signal.h"
#include "hermescom.h"
#include "callback.h"

#ifdef	sigmask
#undef	sigmask
#endif

#define   sigmask(x) (1<<(x-1))

static void childsigh(int);

static TaskStatus tasklist;                   /* task status blocks list head */
static ProcStatus proclist;                /* process status blocks list head */
static CallbackChain cblist=NULL;          /* task state change callback list */
static int exitchan=0;

/*

Function:   inittask

Purpose:    Initialize the module taskmgr.

File:       taskmgr.c

Author:     J.P. Terlouw

Use:        listhead = inittask(ntask, nproc, nsub);

              TaskStatus listhead : pointer to the task status linked list.
                                    After the call to inittask, elements in
                                    this list must be initialized by Hermes.
              int ntask : number of task status blocks to be allocated.
              int nproc : number of process status blocks to be allocated.
              int nsub  : number of keyword substitution blocks to be allocated.

Updates:    Jan 17, 1991, JPT:  created document.

*/

TaskStatus inittask(int ntask, int nproc, int nsub)
{
   TaskStatus curtask;
   ProcStatus curproc;
   int i;

/*
 *   declare child signal handler:
 */
   signal(SIGCHLD,childsigh);

/*
 *   initialize the keyword substitution module:
 */
   sbinit(nsub);

/*
 *   create process status block linked list:
 */
   proclist = (ProcStatus)Calloc(nproc,sizeof(struct _ProcStatus));
   curproc  = proclist;
   while (--nproc) {
     curproc->next = curproc+1;
     curproc++;
   }

/*
 *   create task status block linked list:
 */
   tasklist = (TaskStatus)Calloc(ntask,sizeof(struct _TaskStatus));
   for (i=0; i<ntask; i++) {
      (tasklist+i)->xcallee = Calloc(ntask-1, sizeof(TaskStatus));
   }
   curtask  = tasklist;
   while (--ntask) {
      curtask->next = curtask+1;
      curtask++;
   }

   return tasklist;
}

/*
 * Pidtask determines which task corresponds to a given pid.
 * Only the current process of a task is examined, because processes waiting
 * for a deputy logically cannot exit.
 */
TaskStatus pidtask(int pid)
{
   TaskStatus current;

   for (current=tasklist; current; current = (TaskStatus)(current->next)) {
      if (current->proc && (pid == current->proc->pid)) return current;
   }
   return NULL;
}

/*

Function:     starttask

Purpose:      Start a task or deputy process.

File:         taskmgr.c

Author:       J.P. Terlouw

Use:          result = starttask(task, name);

                       int      result :   1: success  --  0: failure
                       TaskStatus task :   pointer to task status block,
                                           defining the context of the process
                                           to be started.
                       char*      name :   name of the process.

              If 'name' contains an explicit path, starttask only looks there
              to find the executable. If no path is specified, starttask
              first looks in the current directory and then in $gip_exe.
              This function is called exclusively by Hermes.

Updates:      Jan 18, 1991, JPT: created document.

*/

int starttask(TaskStatus task, char *taskname)
{
   char* taskname_i = taskname;
   char  libname[TPLEN];
   char *taskarg;
   ProcStatus newproc;
   int sv[2];
   int stdio[2];
   int i,mask;
   bool explicit;

   if (proclist) {
      newproc = proclist;
      proclist = (ProcStatus)(proclist->next);
   } else newproc = (ProcStatus)Calloc(1,sizeof(struct _ProcStatus));
   newproc->evdr_parent = (task->proc && task->proc->fd_notify);
   newproc->next = task->proc;
   task->proc = newproc;
#if defined(PIPE)
   pipe(stdio);
   (void)fcntl(stdio[0],F_SETFL,O_NDELAY);            /* non-blocking I/O */
   pipe(sv);
   newproc->fd_master  = sv[0];
   newproc->fd_servant = sv[1];
   pipe(sv);
   newproc->fd_omaster  = sv[1];
   isrv = sv[0];
#else
   socketpair(AF_UNIX,SOCK_STREAM,0,stdio);
   (void)fcntl(stdio[0],F_SETFL,O_NDELAY);            /* non-blocking I/O */
   socketpair(AF_UNIX,SOCK_STREAM,0,sv);
   newproc->fd_master  = sv[0];
   newproc->fd_omaster  = sv[0];
   newproc->fd_servant = sv[1];
#endif
   newproc->fd_stdout  = stdio[0];
   newproc->fd_notify  = 0;
   task->reqlen = 0;
   task->reqpid = 0;
   task->state = WAITRUN;
   i=0;
   explicit = FALSE;
   while (i<NAMLEN) {
      if ( *taskname_i == '\0' ) break;
      newproc->name[i] = *(taskname_i++);
      if (newproc->name[i]=='/') {
         i = 0;
         explicit = TRUE;
      } else i++; /* strip directories off */
   }
   newproc->name[i] = 0;
   if (!newproc->next) strcpy(task->name,newproc->name); /*+++ WHY ?? */
#if 0
   reply_free(task);                     /* +++ clear buffer; necessary here? */
#endif

#ifdef	__svr4__
   mask = sighold(SIGCHLD);      /* delay until child registered */
#else
   mask = sigblock(sigmask(SIGCHLD));      /* delay until child registered */
#endif
   newproc->pid = fork();
                                                  /* parent section */
  if (newproc->pid > 0) {
      close(stdio[1]);
#if defined(PIPE)
      close(isrv);
#endif
      if (task->isproc) (*task->isproc)(task);
#ifdef	__svr4__
      (void)sigrelse(SIGCHLD);
#else
      (void)sigsetmask(mask);                 /* child is now registered */
#endif
      return 1;
   }

                                                  /* child section */
  if (newproc->pid == 0) {
      close(newproc->fd_master);
#if defined(PIPE)
      close(newproc->fd_omaster);
#endif /* when a socket pair is used, fd_master and fd_omaster are identical */
      close(0);                     /* no standard input available */
      open("/dev/null", O_RDONLY);
      dup2(stdio[1],1);             /* connect socket to stdout */
      dup2(stdio[1],2);             /* connect socket to stderr */
      dup2(newproc->fd_servant,3);  /* connect socket to fd 3 (reserved) */
#if defined(PIPE)
      dup2(isrv,4);
      close(isrv);
      taskarg = "PI";
#else
      taskarg = "SP";
      close(4);
#endif
      for (i=getdtablesize(); i>4; --i) close(i);
                                            /* close other file descriptors */
      execl(taskname,taskname,taskarg,NULL);     /*        execute task  */
      if (!explicit) {
         strcpy(libname,getenv("gip_exe"));
         strcat(libname,"/");
         strcat(libname,taskname);
         execl(libname,taskname,taskarg,NULL);
      }
      /* +++ trying "gip_exe" necessary for Cola scripts and old xHermes */
      exit(1);                                   /* only if execl failed */
   }

                                                 /* fork failed ... */
   if (newproc->pid < 0) {
      close(newproc->fd_master);
#if defined(PIPE)
      close(newproc->fd_omaster);
#endif /* when a socket pair is used, fd_master and fd_omaster are identical */
      close(newproc->fd_servant);
      close(newproc->fd_stdout);
      close(stdio[1]);
      task->proc = (ProcStatus)(newproc->next); /* unlink from task        */
      newproc->next = proclist;                 /* link into free list     */
      proclist = newproc;
#ifdef	__svr4__
      (void)sigrelse(SIGCHLD);
#else
      (void)sigsetmask(mask);            /* child could not be registered */
#endif
      return 0;
   }
   return 0;
}

/*

Function:     suspend

Purpose:      Suspend a task.

File:         taskmgr.c

Author:       J.P. Terlouw

Use:          suspend(task);

                       TaskStatus task :   pointer to task status block.

              This function is called exclusively by Hermes.

Updates:      Jan 18, 1991, JPT: created document.
*/

void suspend(TaskStatus task)
{
   if (task->state != WAITINP) {
      task->prevstat = task->state;
      task->state = PAUSING;
      kill(task->proc->pid,SIGTSTP);
   }
}

/*

Function:     resume

Purpose:      Resume a suspended process.

File:         taskmgr.c

Author:       J.P. Terlouw

Use:          resume(task);

                       TaskStatus task :   pointer to task status block.

              This function is called exclusively by Hermes.

Updates:      Jan 18, 1991, JPT: created document.

*/
void resume(TaskStatus task)
{
   _ResEvent event;
   
   kill(task->proc->pid,SIGCONT);
   task->state = task->prevstat;
   event.code = RESEVENT;
   NotifyTask(task, (char *)&event, sizeof(event));
}

/*

Function:     aborttask

Purpose:      Abort a task.

File:         taskmgr.c

Author:       J.P. Terlouw

Use:          aborttask(task);

                       TaskStatus task :   pointer to task status block.

              This function is called exclusively by Hermes.

Updates:      Jan 18, 1991, JPT: created document.

*/
void aborttask(TaskStatus task)
{
   if (task->state != NODEFAULT) task->state = ABORTED;
   kill(task->proc->pid,SIGABORT);
}

/*

Function:     rundown

Purpose:      Clean up after a process has exited.

File:         taskmgr.c

Author:       J.P. Terlouw

Use:          rundown(task);

                       TaskStatus task :   pointer to task status block.

              Rundown cleans up the context of an exiting process.
              It reclaims status blocks which become free, closes sockets and
              propagates error conditions to ancestors of the exited process.
              It is called exclusively by Hermes.

Updates:      Jan 18, 1991, JPT: created document.

*/
void rundown(TaskStatus task)
{
   extern int NTASK;
   ProcStatus curproc = task->proc;
   TaskStatus xcallee;
   int i;

   strcpy(task->lstmessage, task->stamessage);    /* save last status message */
   StopNotify(task);
   close(curproc->fd_master);
#if defined(PIPE)
   close(curproc->fd_omaster);
#endif /* when a socket pair is used, fd_master and fd_omaster are identical */
   close(curproc->fd_servant);
   sbback(task);
   if (task->isproc) (*task->isproc)(task);        /* impl. dependent rundown */
   close(curproc->fd_stdout);
   task->proc = (ProcStatus)(curproc->next);       /* unlink from task        */
   curproc->next = proclist;                       /* link into free list     */
   proclist = curproc;
/*
 *   abort all tasks called by this task using xeqcont:
 */
   for (i=0; i<NTASK-1; i++) {
      xcallee = task->xcallee[i];
      if (xcallee) {
         aborttask(xcallee);
         task->xcallee[i] = NULL;
      }
   }
/*
 *   rundown for deputy tasks:
 */
   if (task->proc) {
      sbback(task);
      if (task->state==FINISHED) {
         task->state = RUNNING;
         reply_send(task,DEPUTYSUC);
         (*task->isproc)(task);
      } else if (task->state==STARTFAIL) {
         task->state = RUNNING;
         reply_send(task,NOTPRESENT);
         (*task->isproc)(task);
      } else {
         if (task->proc->fd_notify) {
            int code;
            switch (task->state) {
               case FATAL:     code = DEPFATAL; break;
               case ABORTED:   code = DEPABORT; break;
               case NODEFAULT: code = DEPNODEF; break;
               default:        code = DEPCRASH;
            }
            reply_send(task,code);          /* do not crash event-driven task */
            task->state = RUNNING;
            (*task->isproc)(task);
         } else {
            kill(task->proc->pid,SIGABORT);
         }
      }
   } else {
      TaskStatus current;
      /*
       *  Task waiting for an exited keyword reader may now continue.
       */
      for (current=tasklist; current; current = (TaskStatus)(current->next)) {
         if (current->proc &&
             current->waitkeyread &&
             current->keydest == task) {
            current->waitkeyread = 0;
            reply_send(current, 0);
         }
      }
      /*
       *  Task who started this task via xeqcont is now notified.
       */
      if (task->xcaller) {
         for (current=tasklist; current; current=(TaskStatus)(current->next)) {
            if (current->proc) {
               int i;
               for (i=0; i<NTASK-1; i++) {
                  if (current->xcallee[i]==task) {
                     char statval[10];
                     int  status;
                     switch (task->state) {
                        case FINISHED:  status =  1; break;
                        case STARTFAIL: status = -2; break;
                        case FATAL:     status = -3; break;
                        case CRASHED:   status = -4; break;
                        case ABORTED:   status = -5; break;
                        default:        status = -999; break;
                     }
                     sprintf(statval, "%d", status);
                     insert_par(current, task->xkey, statval, 0);
                     current->xcallee[i] = NULL;
                     break;
                  }
               }
               if (i<NTASK-1) break;
            }
         }
      }
      reply_free(task); /* +++ here ? */
   }
   if (task->parcb) RemoveParProc(task->parcb);
   if (task->edtcb) RemoveEditProc(task->edtcb);
   if (task->keyfile) fclose(task->keyfile);
   if (task->ucamessage) Delete(task->ucamessage);
   TaskChanged(task);
}

/*
 *   Childsigh receives child signals which are generated when a child
 *   process undergoes a state change, like exiting.
 *   It first checks whether it is a process exit, then whether it is
 *   any current process of a task.
 *   In this case it sends a LastRequest message to Hermes on behalf of
 *   the child process. This message will be handled by Hermes in due time.
 */
static void childsigh(int signo)
{
   int            pid;
#if	defined(__sgi__) | defined(__sun__) & defined(__sysv__)
   union {
      int w_status;
   } status;
#else
   union wait     status;
#endif
   TaskStatus     task;
   RequestMessage message;
   struct _LastRequest  request;

while (1) {
#if	defined(__alpha__) | defined(__sgi__) | ( defined(__sun__) & defined(__sysv__) )
      pid = wait3(&status,WNOHANG,NULL);
      signal(SIGCHLD,childsigh);               /* restore registration */
#else
      signal(SIGCHLD,childsigh);               /* restore registration */
#ifdef	__aix__
      pid = wait3(&status,WNOHANG,NULL);
#else
      pid = wait3((int*)&status,WNOHANG,NULL);
#endif
#endif
      if (pid && (pid != -1)) {                /* did wait3 deliver something? */
         task = pidtask(pid);                  /* is child also known ?        */
         if (task) {
            request.code    = LAST;
            request.status  = status.w_status;
            message.reqlen  = sizeof(request);
            message.pid     = pid;
            message.request = (UnitedRequest*)&request;
            local_msg(task,&message);
         } else if (exitchan) {
            int pidstat[2];

            pidstat[0] = pid;
            pidstat[1] = status.w_status;
            write(exitchan,pidstat,sizeof(pidstat));
         }
      } else {
         break;
      }
   }
}

/* ========================================================================== */
/*                           InsertTaskProc                                   */
/* -------------------------------------------------------------------------- */
/* InsertTaskProc registers a procedure to be called whenever the state of
 * a task has changed. The prototype of 'proc' is:
 *
 *    void proc(
 *             int id,       // the identification of the registration
 *             TaskStatus task, // the status block of the affected task
 *             void *arg);  // the argument 'arg' when calling InsertTaskProc
 *
 * At present the only state change which causes the procedures to be called
 * is the termination of (one of) the task's process(es). Callers of this
 * facility however should be written such that a possible future extension
 * of the list of task changes will also be handled correctly, i.e. they
 * should not rely on the present limitation.
 */
extern long InsertTaskProc(TaskProc proc, void *arg)
{
   if (!cblist) cblist=NewCbChain();
   return (long)RegisterCb(cblist,(CallbackProc)proc,arg);
}

/* ========================================================================== */
/*                           RemoveTaskProc                                    */
/* -------------------------------------------------------------------------- */
/* RemoveTaskProc removes the registration of a procedure registered by
 * calling InsertTaskProc.
 */
extern void RemoveTaskProc(long id)
{
   DeregisterCb(cblist,(CallbackId)id);
}

/* ========================================================================== */
/*                              TaskChanged                                   */
/* -------------------------------------------------------------------------- */
/* TaskChanged calls all registered task change handlers.
 */
extern void TaskChanged(TaskStatus task)
{
   InvokeCb(cblist,task);
}

/* ========================================================================== */
/*                            ExitChan                                        */
/* -------------------------------------------------------------------------- */
/*  ExitChan specifies or clears the file descriptor through which the
 *  termination of non-task processes is reported.
 */
extern void ExitChan(int fd)
{
   if (fd) {
      if (exitchan) {
         printf("\n\n ExitChan call error \n\n");
         exit(1);
      } /* +++ defensive test - remove later */
      exitchan = fd;
   } else {
      int tmp=exitchan;
      exitchan = 0;
      close(tmp);
   }
}

/* ========================================================================== */
/*                            NotifyCaller                                    */
/* -------------------------------------------------------------------------- */
/*  NotifyCaller notifies the xeqcont() caller about certain events in the
 *  called task. This is done by setting the value of one or more keyword
 *  derived from the keyword specified in the xeqcont() call, by prefixing
 *  it by a two-character code.
 */
extern void NotifyCaller(TaskStatus task, int mode)
{
   char keymsg[KEYLEN+1], typecode[3];
   
   if (!task->xcaller) return;
   switch (mode) {

      case 1:                                     /* waiting for input       */
      case 3:                                     /* BAD INPUT or NO DEFAULT */
      
         sprintf(keymsg, "R_%s", task->xkey);         /* reject message text */
         if (mode==3 || (task->rejected && *task->rejected)) {
            insert_par(task->xcaller, keymsg, task->ucamessage, 0);
         } else { 
            insert_par(task->xcaller, keymsg, "", 0);
         }
         sprintf(keymsg, "M_%s", task->xkey);             /* request message */
         insert_par(task->xcaller, keymsg,
                    ((UserinpRequest)&task->request)->message, 0);
         sprintf(keymsg, "T_%s",  task->xkey);               /* request type */
         sprintf(typecode, "%d", ((UserinpRequest)&task->request)->type);
         insert_par(task->xcaller, keymsg, typecode, 0);
         sprintf(keymsg, "K_%s", task->xkey);             /* request keyword */
         insert_par(task->xcaller, keymsg,
                    ((UserinpRequest)&task->request)->key, 0);
         break;

      case 2:                                              /* status message */

         sprintf(keymsg, "S_%s", task->xkey);
         insert_par(task->xcaller, keymsg, task->stamessage, 0);
         break;
   } 
}
#<

#>            taskoutput.c
/* taskoutput.c
                              COPYRIGHT (c) 1993
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes.
Author: J.P. Terlouw
*/

#include <unistd.h>

#include "hermescom.h"
#include "mainloop.h"
#include "taskoutput.h"

static int TaskOutput(long id, int fd, TaskStatus task);
static void OutBuffer(TaskStatus task);

/* ========================================================================== */
/*                             EnableTskOut                                   */
/* -------------------------------------------------------------------------- */
/*  EnableTskOut 'activates' static routine TaskOutput so that it translates
 *  task writes to stdout and stderr into internal calls to anyout.
 */
extern void EnableTskOut(TaskStatus task)
{
   if (!task->proc->id_stdout) {
      task->proc->id_stdout =
         InsertMessageProc((void(*)())TaskOutput,task->proc->fd_stdout,task);
   }
}
      
/* ========================================================================== */
/*                             DisableTskOut                                  */
/* -------------------------------------------------------------------------- */
/*  DisableTskOut() 'deactivates' TaskOutput.
 */
extern void DisableTskOut(TaskStatus task)
{
   if (task->proc->id_stdout) {
      while (TaskOutput(task->proc->id_stdout,task->proc->fd_stdout,task)>0) {;}
      RemoveMessageProc(task->proc->id_stdout);
      task->proc->id_stdout = 0;
   }
}

/* -------------------------------------------------------------------------- */
/*                                TaskOutput                                  */
/* -------------------------------------------------------------------------- */
/*  Routine called by mainloop() whenever a servant task writes to its
 *  standard  output or standard error file descriptor.
 *  This information is read from the socket to which the file descriptor
 *  has been redirected and buffered in the task's stdout buffer. 
 *  Then OutBuffer() is called to process the information read.
 */

static int TaskOutput(long id, int fd, TaskStatus task)
{
   int space=LINLEN - task->stdo_in, nread;
   
   nread = read(task->proc->fd_stdout,&task->stdobuf[task->stdo_in],space);
   if (nread>0) {
      task->stdo_in += nread;
   }
   OutBuffer(task);
   return nread;
}

/* -------------------------------------------------------------------------- */
/*                               OutBuffer                                    */
/* -------------------------------------------------------------------------- */
/*  OutBuffer scans the task's standard output buffer and sends every line in 
 *  the buffer to both the screen and the logfile. If there are no newline-
 *  separated lined in the buffer and the buffer is full, it is written out
 *  unconditionally; if it is empty, nothing is done.
 */
static void OutBuffer(TaskStatus task)
{
   int    i, out=0;

   if (task->stdo_in==0) return;                              /* buffer empty */
                                                              /* normal case */
   for (i=0; i<task->stdo_in; i++) {
      if (task->stdobuf[i] == '\n' ) {
         task->stdobuf[i] = '\0';
         put_coa(task,task->stdobuf+out);
         put_log(task,task->stdobuf+out);
         out = i+1;
      }
   }
                                      /* move remaining data to front */
   if (out) {
      char *bufpt=task->stdobuf;
      for (i=out; i<task->stdo_in; i++) *(bufpt++) = task->stdobuf[i];
      task->stdo_in = bufpt - task->stdobuf;
   }
                                      /* buffer still full: flush completely  */
   if (task->stdo_in==LINLEN) {
      task->stdobuf[LINLEN] = '\0';
      task->stdo_in = 0;
      put_coa(task,task->stdobuf);
      put_log(task,task->stdobuf);
   }
}
#<

#>            dcdparse.y
%{
/* dcdparse.y


                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

Parser for numeric user input.
Author: J.P. Terlouw

                                 NOTICE

The current makefiles call yacc only on hp9000s700 systems. Other
architectures compile dcdparse.c without calling yacc.
*/

#include "gipsyc.h"
#include "stdlib.h"
#include "stdio.h"
#include "ctype.h"
#include "math.h"
#include "float.h"
#include "setfblank.h"
#include "setdblank.h"
#include "fblank.h"
#include "dblank.h"
#include "callback.h"
#include "dcdcom.h"
#include "varmgr.h"
#include "dcdgds.h"
#include "gds_closeall.h"
#include "dcderrstr.h"
#include "dcdfile.h"
#include "dcdscan.h"
#include "dcdparse.h"

#define  ERRCHK if (errcode) {  YYERROR; }
#define  SETERR(n) if (!errcode) errcode = n
#define  ELEM(var,index) (var.type==DOUBLE?var.value:var.values[i])
#define  yyparse dcd_parse             /* define own name */
#define  yyerror dcd_error

typedef struct {
   char   *fname;       /* function name */
   Value  (*fun)();     /* pointer to function */
} valfun;

typedef struct {
   char   *fname;       /* function name */
   int    nargs;        /* number of arguments */
   double (*fun)();     /* pointer to function */
} arfun;

typedef double (*Monop)(double);
typedef double (*Binop)(double, double);

static int do_parse(char *expr);

static Value fun(Value a, Arglst b);
static Value lst(Value a, Value b);
static Value lsti(Value a, Value b, Value c);
static Value lstn(Value a, Value b);
static Value lstsel(Value a, Value b);
static Value var(Value a);  
static Value num(Value a);
static void  asn(Value, Value b);
static Value monop(Monop op, Value a);
static Value binop(Binop op, Value a, Value b);
static Value tetrop(Binop op, Value a, Value b, Value c, Value d);
static double add_i(double arg1, double arg2);
static double sub_i(double a, double b);
static double mul_i(double a, double b);
static double div_i(double a, double b);
static double pwr(double a, double b);
static double neg_i(double a);
static double abs_i(double a);
static double sqrt_i(double a);
static double sin_i(double a);
static double sind_i(double a);
static double asin_i(double a);
static double asind_i(double a);
static double cos_i(double a);
static double cosd_i(double a);
static double acos_i(double a);
static double acosd_i(double a);
static double tan_i(double a);
static double tand_i(double a);
static double atan_i(double a);
static double atand_i(double a);
static double exp_i(double a);
static double sinh_i(double a);
static double ln_i(double a);
static double cosh_i(double a);
static double log_i(double a);
static double tanh_i(double a);
static double rad_i(double a);
static double deg_i(double a);
static double erf_i(double a);
static double erfc_i(double a);
static double max_i(double a, double b);
static double min_i(double a, double b);
static double sinc_i(double a);
static double atan2_i(double a, double b);
static double atand2_i(double a, double b);
static double sign_i(double a);
static double mod_i(double a, double b);
static double int_i(double a);
static double nint_i(double a);
static double ranu_i(double a, double b);
static double ranp_i(double a);
static double rang_i(double a, double b);
static double ifgt(double a, double b);
static double iflt(double a, double b);
static double ifge(double a, double b);
static double ifle(double a, double b);
static double ifeq(double a, double b);
static double ifne(double a, double b);

static Value lstcount(Arglst b);
static Value lstmean(Arglst b);
static Value lstsum(Arglst b);

static int    errcode=0;
static Arglst arg;
static double blankvalue=0.0;
static Value  NullVal={NONE,0,0.0,NULL};
static Value  UnitVal={DOUBLE,0,1.0,NULL};
static Value  resval;

static CallbackChain cbchain=NULL;

/*
 *   Functions to be called from monop(), binop() or tetrop().
 *   If the number of arguments is -1, the function is called directly.
 *   In this case it knows its number of arguments itself.
 */
static arfun arfuns[]={
   {"ABS",    1, abs_i},
   {"SQRT",   1, sqrt_i},
   {"SIN",    1, sin_i},
   {"SIND",   1, sind_i},
   {"ASIN",   1, asin_i},
   {"ASIND",  1, asind_i},
   {"COS",    1, cos_i},
   {"COSD",   1, cosd_i},
   {"ACOS",   1, acos_i},
   {"ACOSD",  1, acosd_i},
   {"TAN",    1, tan_i},
   {"TAND",   1, tand_i},
   {"ATAN",   1, atan_i},
   {"ATAND",  1, atand_i},
   {"EXP",    1, exp_i},
   {"SINH",   1, sinh_i},
   {"LN",     1, ln_i},
   {"COSH",   1, cosh_i},
   {"LOG",    1, log_i},
   {"TANH",   1, tanh_i},
   {"RAD",    1, rad_i},
   {"DEG",    1, deg_i},
   {"ERF",    1, erf_i},
   {"ERFC",   1, erfc_i},
   {"MAX",    2, max_i},
   {"MIN",    2, min_i},
   {"SINC",   1, sinc_i},
   {"ATAN2",  2, atan2_i},
   {"ATAND2", 2, atand2_i},
   {"SIGN",   1, sign_i},
   {"MOD",    2, mod_i},
   {"INT",    1, int_i},
   {"NINT",   1, nint_i},
   {"RANU",   2, ranu_i},
   {"RANG",   2, rang_i},
   {"RANP",   1, ranp_i},
   {"IFGT",   4, ifgt},
   {"IFLT",   4, iflt},
   {"IFGE",   4, ifge},
   {"IFLE",   4, ifle},
   {"IFEQ",   4, ifeq},
   {"IFNE",   4, ifne}
};

static valfun valfuns[]={
   {"COUNT",  lstcount},
   {"MEAN",   lstmean},
   {"SUM",    lstsum}
};

%}

%start stat

%union {
       Value  valval;
       Arglst argval;
       }


%token <valval> NUMBER IDENTIFIER STRING
%token          POWER WHITE LEXERR TEXTFILE DESCR IMAGE TABLE UMINUS REPEAT
%token          BADNUM

%type <valval> expr liter gdslev list lintro
%type <argval> arglst

%right  '='
%left   ','
%left   WHITE
%left  ':'
%left   REPEAT
%left   '+' '-' '?'
%left   '*' '/'
%right  POWER
%left   UMINUS


%%

/* ---------------------------    STATEMENT       --------------------------- */
stat   : expr '\n'
            { resval =$1; return 0; }
       | IDENTIFIER '=' expr '\n'
            { asn( $1, $3); resval = $3;  ERRCHK; return 0; }
       | STRING '=' expr '\n'
            { asn( $1, $3); resval = $3;  ERRCHK; return 0; }
       | error '\n'
            {
               int result;
            
                result = errcode;
                errcode = 0;
                yyerrok;
                return result;
            }
       ;


/* ---------------------------   EXPRESSION       --------------------------- */
expr   : '(' expr ')'                       /* parenthesized expression      */
            { $$ = $2; }
       | expr '?' expr                      /* list selection                */
            { $$ = lstsel($1,$3); ERRCHK }
       | expr WHITE expr                    /* list build-up                 */
            { $$ = lst($1,$3);if (errcode) { FreeVar(&$$); YYERROR; } }
       | expr REPEAT expr                   /* 'repeat' list                 */
            { $$ = lstn($1,$3); ERRCHK }
       | expr '+' expr                      /* addition                      */
            { $$ = binop(add_i, $1, $3); ERRCHK }
       | expr '-' expr                      /* subtraction                   */
            { $$ = binop(sub_i, $1, $3); ERRCHK }
       | expr '*' expr                      /* multiplication                */
            { $$ = binop(mul_i, $1, $3); ERRCHK }
       | expr '/' expr                      /* division                      */
            { $$ = binop(div_i, $1, $3); ERRCHK }
       | expr POWER expr                    /* exponentiation                */
            { $$ = binop(pwr, $1, $3); ERRCHK }
       | STRING '(' arglst ')'              /* mathematical function         */
            { $$ = fun($1, $3); ERRCHK }
       | TEXTFILE '(' liter ',' expr ',' gdslev ')' /* file function          */
            { $$ = dcd_file($3, $5, $7); ERRCHK }
       | DESCR '(' liter WHITE  gdslev ',' liter ')'  /* GDS header function  */
            { $$ = dcd_descr($3, $5, $7); ERRCHK }
       | DESCR '(' liter  ',' liter ')'               /* GDS header function  */
            { $$ = dcd_descr($3, NullVal, $5); ERRCHK }
       | TABLE '(' liter WHITE  gdslev ',' liter ',' liter ',' gdslev ')'
            { $$ = dcd_table($3, $5, $7, $9, $11); ERRCHK }
       | TABLE '(' liter  ',' liter ',' liter ',' gdslev ')'
            { $$ = dcd_table($3, NullVal, $5, $7, $9); ERRCHK }
       | IMAGE '(' liter WHITE  gdslev ',' gdslev ')' /* GDS image function  */
            { $$ = dcd_image($3, $5, $7); ERRCHK }
       | IMAGE '(' liter  ',' gdslev ')'             /* GDS image function  */
            { $$ = dcd_image($3, NullVal, $5); ERRCHK }
       | '-' expr %prec UMINUS              /* unary minus                   */
            { $$ = monop(neg_i, $2); ERRCHK }
       | '+' expr %prec UMINUS              /* unary plus                    */
            { $$ = $2;  }
       | IDENTIFIER                         /* variable                      */
            { $$ = var($1); }
       | NUMBER                             /* number                        */
            { $$ = $1; }
       | list
            { $$ = $1; }
       | LEXERR                             /* error detected by scanner     */
            { errcode = BADSSYN; ERRCHK }
       | BADNUM                             /* error detected by scanner     */
            { errcode = FLOVUF; ERRCHK }
       ;

list   : lintro ':' expr
            { $$ =  lsti($1,resval,$3); ERRCHK }
       | lintro
            { $$ =  lsti($1,resval,UnitVal); ERRCHK }
       ;

lintro : expr ':' expr
            { $$ = $1; resval = $3; }
       ;

liter  :  { dcdmode(1);} STRING
            { $$ = $2; dcdmode(0); }
       ;


gdslev :  { dcdmode(2);} STRING
            { $$ = $2; dcdmode(0); }
       ;

arglst : expr
            { arg.num = 1; arg.val[0] = $1; $$ = arg; }
       | arglst ',' expr
            { if (arg.num>=MAXARG) YYERROR;
              arg.val[arg.num] = $3;
              arg.num++;
              $$ = arg; }
       ;


%%


/* ========================================================================== */
/*                                 EvalExpr                                   */
/* -------------------------------------------------------------------------- */
/*  EvalExpr() evaluates an expression. Its purpose is to allow Hermes
 *  to support assignment statements.
 */
extern int EvalExpr(char *expr)
{
   int result;
   
   if (!blankvalue) blankvalue = GetVar("BLANK").value;
   result = do_parse(expr);
   CleanVar();
   gds_closeall_c();
   return result;
}

/* ========================================================================== */
/*                                 DecodeReal                                 */
/* -------------------------------------------------------------------------- */
/*  DecodeReal() decodes the string present in the argument 'expr'.
 *  The resulting numbers are returned via the argument 'values'.
 *  Argument 'number' specifies the maximum expected number of values.
 *  The function returns the number of values found, or a negative
 *  error code.
 */
extern int DecodeReal(char *expr, float *values, int number, int *error)
{
   int result;
   
   if (!blankvalue) blankvalue = GetVar("BLANK").value;
   MaxList(number);
   *error = do_parse(expr);
   if (!*error) {
      if (resval.type==DOUBLE) {
         result = 1;
         if (resval.value==blankvalue) setfblank_c(values);
         else if (resval.value>( MAXFLOAT)) *values =  FLT_MAX;
         else if (resval.value<(-MAXFLOAT)) *values = -FLT_MAX;
         else *values = resval.value;
      } else {
         result = resval.num;
         if (result>number) {
            *error = BUFOVF;
         } else {
            int i;
            for (i=0; i<result; i++) {
               double curval=resval.values[i];
               if (curval==blankvalue) 
                  setfblank_c(&values[i]);
                  else if (curval>( MAXFLOAT)) values[i] =  FLT_MAX;
                  else if (curval<(-MAXFLOAT)) values[i] = -FLT_MAX;
               else 
                  values[i] = resval.values[i];
            }
         }
      }
   } else result = -1;
   CleanVar();
   gds_closeall_c();
   MaxList(0);
   return result;
}

/* ========================================================================== */
/*                                 DecodeFint                                 */
/* -------------------------------------------------------------------------- */
/*  DecodeFint() decodes the string present in the argument 'expr'.
 *  The resulting numbers are returned via the argument 'values'.
 *  Argument 'number' specifies the maximum expected number of values.
 *  The function returns the number of values found, or a negative
 *  error code.
 */
extern int DecodeFint(char *expr, fint *values, int number, int *error)
{
   int result;
   
   if (!blankvalue) blankvalue = GetVar("BLANK").value;
   MaxList(number);
   *error = do_parse(expr);
   if (!*error) {
      if (resval.type==DOUBLE) {
         result = 1;
         if (resval.value==blankvalue) *values = 0;
         else if (resval.value>( MAXFLOAT)) *values = MAXINT;
         else if (resval.value<(-MAXFLOAT)) *values = MININT;
         else if (resval.value<MININT || resval.value>MAXINT) *error = BADCONV;
         else *values = resval.value;
      } else {
         result = resval.num;
         if (result>number) {
            *error = BUFOVF;
         } else {
            int i;
            for (i=0; i<result; i++) {
               double curval=resval.values[i];
               if (curval==blankvalue) 
                  values[i] = 0;
               else if (curval>( MAXFLOAT)) values[i] = MAXINT;
               else if (curval<(-MAXFLOAT)) values[i] = MININT;
               else if (curval<MININT || curval>MAXINT) {
                  *error = BADCONV;
                  break;
               } else values[i] = resval.values[i];
            }
         }
      }
   } else result = -1;
   CleanVar();
   gds_closeall_c();
   MaxList(0);
   return result;
}

/* ========================================================================== */
/*                                 DecodeDouble                               */
/* -------------------------------------------------------------------------- */
/*  DecodeDouble() decodes the string present in the argument 'expr'.
 *  The resulting numbers are returned via the argument 'values'.
 *  Argument 'number' specifies the maximum expected number of values.
 *  The function returns the number of values found, or a negative
 *  error code.
 */
extern int DecodeDouble(char *expr, double *values, int number, int *error)
{
   int result;
   
   if (!blankvalue) blankvalue = GetVar("BLANK").value;
   MaxList(number);
   *error = do_parse(expr);
   if (!*error) {
      if (resval.type==DOUBLE) {
         result = 1;
         if (resval.value==blankvalue) setdblank_c(values);
         else if (resval.value>( MAXFLOAT)) *values =  DBL_MAX;
         else if (resval.value<(-MAXFLOAT)) *values = -DBL_MAX;
         else *values = resval.value;
      } else {
         result = resval.num;
         if (result>number) {
            *error = BUFOVF;
         } else {
            int i;
            for (i=0; i<result; i++) {
               double curval=resval.values[i];
               if (curval==blankvalue) 
                  setdblank_c(&values[i]);
               else if (curval>( MAXFLOAT)) values[i] =  DBL_MAX;
               else if (curval<(-MAXFLOAT)) values[i] = -DBL_MAX;
               else 
                  values[i] = resval.values[i];
            }
         }
      }
   } else result = -1;
   CleanVar();
   gds_closeall_c();
   MaxList(0);
   return result;
}
            
/* ========================================================================== */
/*                                 dcderror                                   */
/* -------------------------------------------------------------------------- */
/*  dcderror() is used by 'satellite' routines of this parser to report
 *  any errors detected.
 */
extern void dcderror(int code)
{
   if (!errcode) errcode = code;
}

/* ========================================================================== */
/*                                 InsertExprProc                             */
/* -------------------------------------------------------------------------- */
/* InsertExprProc() inserts a callback to be called whenever an expression
 * is evaluated. Only one callback can be defined at the moment.
 */
extern long InsertExprProc(ExprProc proc, void *data)
{
   if (!cbchain) cbchain = NewCbChain();
   return (long)RegisterCb(cbchain,(CallbackProc)proc,data);
}

/* ========================================================================== */
/*                                 RemoveExprProc                             */
/* -------------------------------------------------------------------------- */
extern void RemoveExprProc(long id)
{
   DeregisterCb(cbchain,(CallbackId)id);
}
 
/* -------------------------------------------------------------------------- */
/*                                 do_parse                                   */
/* -------------------------------------------------------------------------- */
/*  do_parse() takes care of parsing the expression supplied as its argument.
 *  If necessary, it calls any registered callback.
 */
static int do_parse(char *expr)
{
   int result;
   char first[80], last[80];

   dcdinit(expr);
   result = yyparse();
   if (cbchain) {
      char    valstr[81];
      char    *errmsg;
      ExprVal state;
      state.expr  = expr;
      state.value = valstr;
      if (result) {
         errmsg = dcderrstr(result);
         strncpy(valstr,errmsg,strlen(errmsg)+1);
      } else if (resval.type==DOUBLE) {
         if (resval.value==blankvalue) strcpy(valstr,"BLANK");
         else if (resval.value>( MAXFLOAT)) strcpy(valstr,"INF");
         else if (resval.value<(-MAXFLOAT)) strcpy(valstr,"-INF");
         else sprintf(valstr,"%lg",resval.value);
      } else if (resval.type==LIST) {
         if (resval.values[0]==blankvalue) strcpy(first,"BLANK");
         else if (resval.values[0]>( MAXFLOAT)) strcpy(first,"INF");
         else if (resval.values[0]<(-MAXFLOAT)) strcpy(first,"-INF");
         else sprintf(first,"%lg",resval.values[0]);
         if (resval.values[resval.num-1]==blankvalue) strcpy(last,"BLANK");
         else if (resval.values[resval.num-1]>( MAXFLOAT)) strcpy(last,"INF");
         else if (resval.values[resval.num-1]<(-MAXFLOAT)) strcpy(last,"-INF");
         else sprintf(last,"%lg",resval.values[resval.num-1]);
         sprintf(valstr,"List(%d): first= %s, last= %s",
                 resval.num, first, last);
      } else strcpy(valstr,"--- Internal error ---");
      InvokeCb(cbchain,(void*)&state);
   }
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lst                                        */
/* -------------------------------------------------------------------------- */
/*  lst() concatenates its arguments and returns the resulting list.
 */
static Value lst(Value a, Value b)
{
   Value result;
   int   i;
   
   if (a.type==DOUBLE && b.type==DOUBLE) {
      result = NewList(2);
      result.values[0] = a.value;
      result.values[1] = b.value;
   }
   else if (a.type==LIST && b.type==DOUBLE) {
      result = NewList(a.num+1);
      if (!errcode) {
         for (i=0; i<a.num; i++) result.values[i] = a.values[i];
         result.values[i] = b.value;
      }
      FreeVar(&a);
   }
   else if (a.type==DOUBLE && b.type==LIST) {
      result = NewList(b.num+1);
      if (!errcode) {
         result.values[0] = a.value;
         for (i=0; i<b.num; i++) result.values[i+1] = b.values[i];
      }
      FreeVar(&b);
   }
   else if (a.type==LIST && b.type==LIST) {
      result = NewList(a.num+b.num);
      if (!errcode) {
         for (i=0; i<a.num; i++) result.values[i] = a.values[i];
         for (i=0; i<b.num; i++) result.values[i+a.num] = b.values[i];
      }
      FreeVar(&a);
      FreeVar(&b);
   } else errcode = BADOPER;
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lsti                                       */
/* -------------------------------------------------------------------------- */
static Value lsti(Value a, Value b, Value c)
{
   Value result;
   double incr;
   int    i;
    
   if (a.type!=DOUBLE || b.type!=DOUBLE || c.type!=DOUBLE) {
      errcode = BADELEM;
      return result;
   }
   if (a.value==blankvalue || b.value==blankvalue || c.value==blankvalue) {
      errcode = BADELEM;
      return result;
   }
   if (a.value>MAXFLOAT || b.value>MAXFLOAT || c.value>MAXFLOAT) {
      errcode = BADELEM;
      return result;
   }
   if (a.value<(-MAXFLOAT) || b.value<(-MAXFLOAT) || c.value<(-MAXFLOAT)) {
      errcode = BADELEM;
      return result;
   }
   incr = fabs(c.value);
   if (a.value>b.value) incr = -incr;
   result =NewList((int)((b.value-a.value)/incr)+1);
   if (!errcode) for (i=0; i<result.num; i++) {
      result.values[i] = a.value + i*incr;
   }
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lstn                                       */
/* -------------------------------------------------------------------------- */
static Value lstn(Value a, Value b)
{
   Value result;
   int   nelems, i;
    
   if (a.type!=DOUBLE || b.type!=DOUBLE) {
   /* ... allow repetition of list? */
      errcode = BADELEM;
      return result;
   }
   if (b.value==blankvalue) {
      errcode = BADINCR;
      return result;
   }
   nelems = (int)b.value;
   if (nelems<1) {
      result.type = NONE;
      errcode = BADINCR;
      return result;
   }
   if (nelems==1) return a;
   result = NewList(nelems);
   if (!errcode) for (i=0; i<nelems; i++) result.values[i] = a.value;
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lstsel                                     */
/* -------------------------------------------------------------------------- */
static Value lstsel(Value a, Value b)
{
   Value result;
   int    i, index;
    
   if (a.type==DOUBLE) {
      errcode = BADOPER;
      return result;
   }
   if (b.type==DOUBLE) {
      index = (int)nint_i(b.value)-1;
      if (index<0 || index>=a.num) {
         errcode = BADINDX;
      } else {
         result.type = DOUBLE;
         result.value = a.values[index];
      }
   } else if (b.type==LIST) {
      result = b;
      for (i=0; i<b.num; i++) {
         index = (int)nint_i(b.values[i])-1;
         if (index<0 || index>=a.num) {
            errcode = BADINDX;
            break;
         }
         b.values[i] = a.values[index];
      }
   } else errcode = BADOPER;
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lstcount                                   */
/* -------------------------------------------------------------------------- */
/*  lstcount() returns the number of elements in a LIST or DOUBLE.
 */
static Value lstcount(Arglst a)
{
   Value result=NullVal;    

   if (a.num != 1) {
      errcode = BADNARG;
   } else if (a.val[0].type==DOUBLE) {
      result = UnitVal;
   } else if (a.val[0].type==LIST) {
      result.type = DOUBLE;
      result.value = a.val[0].num;
   } else errcode = BADARG;
   FreeVar(&a.val[0]);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lstmean                                    */
/* -------------------------------------------------------------------------- */
/*  lstmean() returns the average of the elements in a LIST or DOUBLE.
 */
static Value lstmean(Arglst a)
{
   Value   result;
   int     n;

   if (a.val[0].type==DOUBLE) n = 1; else n = a.val[0].num;
   result = lstsum(a);
   if (result.type==DOUBLE) result.value = div_i(result.value, (double)n);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 lstsum                                     */
/* -------------------------------------------------------------------------- */
/*  lstsum() returns the sum of the elements in a LIST or DOUBLE.
 */
static Value lstsum(Arglst a)
{
   Value result=NullVal;
   int   i;

   if (a.num != 1) {
      errcode = BADNARG;
   } else if (a.val[0].type==DOUBLE) {
      result = a.val[0];
   } else if (a.val[0].type==LIST) {
      result.type = DOUBLE;
      for (i=0; i<a.val[0].num; i++) {
         result.value = add_i(result.value, a.val[0].values[i]);
      }
   } else errcode = BADARG;
   FreeVar(&a.val[0]);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 monop                                      */
/* -------------------------------------------------------------------------- */
static Value monop(Monop op, Value a)
{
   if (a.type==DOUBLE) {
      a.value = (*op)(a.value);
   } else if (a.type==LIST) {
      int i;
      for (i=0; i<a.num; i++) a.values[i] = (*op)(a.values[i]);
   } else errcode = BADARG;
   if (errcode) FreeVar(&a);
   return a;
}

/* -------------------------------------------------------------------------- */
/*                                 binop                                      */
/* -------------------------------------------------------------------------- */
static Value binop(Binop op, Value a, Value b)
{
   Value result;
    
   if (a.type==DOUBLE && b.type==DOUBLE) {
      result.type = DOUBLE;
      result.value = (*op)(a.value, b.value);
   } else if (a.type==DOUBLE && b.type==LIST) {
      int i;
      result = b;
      for (i=0; i<result.num; i++) {
         result.values[i] = (*op)(a.value, b.values[i]);
      }
      return result;
   } else if (a.type==LIST && b.type==DOUBLE) {
      int i;
      result = a;
      for (i=0; i<result.num; i++) {
         result.values[i] = (*op)(a.values[i], b.value);
      }
   } else if (a.type==LIST && b.type==LIST) {
      int i;
      if (a.num != b.num) {
         errcode = BADLSTL;
         return result;
      }
      result = a;
      for (i=0; i<result.num; i++) {
         result.values[i] = (*op)(a.values[i], b.values[i]);
      }
      FreeVar(&b);
   } else errcode = BADOPER;
   if (errcode) FreeVar(&result);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 tetrop                                     */
/* -------------------------------------------------------------------------- */
/*  tetrop() handles a four-argument function. The only four-argument 
 *  functions at present are the if-functions. The code of this function
 *  is only capable of handling these functions.
 */
static Value tetrop(Binop op, Value a, Value b, Value c, Value d)
{
   Value result;
   Value compare;
   int   nelems, i;
    
   compare = binop(op, a, b);
   if (errcode) return result;
   if (compare.type==DOUBLE) {
      if (compare.value==blankvalue) return compare;
      else return compare.value?c:d;
          /* ... Type and number are not checked here. Do we want this? */ 
   }
   nelems = compare.num;
   if (c.type==LIST && c.num!=nelems || d.type==LIST && d.num!=nelems) {
      errcode = BADLSTL;
      FreeVar(&compare);
      return result;
   }
   result = NewList(nelems);
   for (i=0; i<nelems; i++) {
      if (compare.values[i]==blankvalue) result.values[i] = blankvalue;
      else result.values[i] = compare.values[i]?ELEM(c,i):ELEM(d,i);
   }
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 fun                                        */
/* -------------------------------------------------------------------------- */
/*  fun() looks up the function of which the name is specified in argument 'a'.
 *  If found, it checks the number of arguments and calls one of the
 *  functions monop, binop er tetrop.
 */
static Value fun(Value a, Arglst b)
{
   Value result;
   int i, nfuns=sizeof(arfuns)/sizeof(arfun);
   char *fname=(char*)a.values;
   
                                                           /* Value function? */
   for (i=0; fname[i]; i++) fname[i] = toupper(fname[i]);
   nfuns = sizeof(valfuns)/sizeof(valfun);
   for (i=0; i<nfuns; i++) {
      if (!strcmp(valfuns[i].fname,fname)) {
         result = (*valfuns[i].fun)(b);
         break;
      }
   }
                                                      /* arithmetic function? */
   if (i==nfuns) nfuns = sizeof(arfuns)/sizeof(arfun); else nfuns = -1;
   for (i=0; i<nfuns; i++) {
      if (!strcmp(arfuns[i].fname,fname)) {
         if (b.num != arfuns[i].nargs) {
            errcode = BADNARG;
         } else {
            switch (b.num) {
               case  1: result = monop((Monop)arfuns[i].fun, b.val[0]);
                        break;
               case  2: result = binop((Binop)arfuns[i].fun,
                                        b.val[0], b.val[1]); break;
               case  4: result = tetrop((Binop)arfuns[i].fun,
                                         b.val[0], b.val[1],
                                         b.val[2], b.val[3]); break;
               default: errcode = BADNARG; break;
            }
         }
         break;
      }
   }
   if (i==nfuns) errcode = BADFUN;
   FreeVar(&a);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 var                                        */
/* -------------------------------------------------------------------------- */
/*  var() returns the value of the variable of which the name is specified
 *  in argument 'a'.
 */
static Value var(Value a)
{
   Value result;
    
   result = GetVar((char*)a.values);
   FreeVar(&a);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 asn                                        */
/* -------------------------------------------------------------------------- */
/*  asn() assigns the value in argument 'b' to the variable of which the name
 *  is specified in argument 'a'.
 */
static void  asn(Value a, Value b)
{
   errcode = PutVar((char*)a.values, b);
   FreeVar(&a);
}

yyerror (char *s)
{
   dcdmode(0);
   errcode = BADPSYN;
}

/* -------------------------------------------------------------------------- */
/*                   A R I T H M E T I C   F U N C T I O N S                  */
/* -------------------------------------------------------------------------- */
static double neg_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return -arg1;
   }
}

static double add_i(double arg1, double arg2)
{
   double result;

   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      result = blankvalue;
   } else {
      result = arg1+arg2;
      if (fabs(result)>MAXFLOAT) {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
   return result;
}

static double sub_i(double arg1, double arg2)
{
   double result;
   
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      result = blankvalue; 
   } else {
      result = arg1-arg2;
      if (fabs(result)>MAXFLOAT) {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
   return result;
}

static double mul_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else if ((arg1 == 0.0) || (arg2 == 0.0)) {
      return 0.0;
   } else {
      double intsum = (log10(fabs(arg1))+log10(fabs(arg2)));
      if ((MINLOG < intsum) && (intsum < MAXLOG)) {
         return arg1*arg2;
      } else {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
}
      
static double div_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else if (arg2 == 0.0) {
      SETERR(ERARITH);
      return blankvalue;
   } else if (arg1 == 0.0) {
      return 0.0;
   } else {
      double intsum = (log10(fabs(arg2))-log10(fabs(arg1)));
      if ((MINLOG < intsum)&&(intsum < MAXLOG)) {
         return arg1/arg2;
      } else {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
}

static double pwr(double arg1, double arg2)
{
   double intsum;
   
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) return blankvalue;
   if (arg1==0.0) {
      if (arg2>0) return 0.0;
      else {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
   intsum = log10(fabs(arg1))*arg2;
   if (intsum>MAXLOG || intsum<MINLOG) {
      SETERR(ERARITH);
      return blankvalue;
   }
   if (arg1 >= 0.0) {
      return pow(arg1,arg2);
   } else {
      int p = arg2, t;
      double epsilon = 0.000001;
      if (fabs(arg2 - p) <= epsilon) {
         t = (p % 2 == 0) ? 1 : -1;
         return t*pow(fabs(arg1),arg2);
      } else {
         SETERR(ERARITH);
         return blankvalue;
      }
   }
}

static double sin_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return sin(arg1);
   }
}

static double sind_i(double arg1)
{
   return sin_i(rad_i(arg1));
}

static double asin_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) > 1) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return asin(arg1);
   }
}

static double asind_i(double arg1)
{
   return deg_i(asin_i(arg1));
}

static double sinh_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) > 70) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return sinh(arg1);
   }
}

static double cos_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return cos(arg1);
   }
}

static double cosd_i(double arg1)
{
   return cos_i(rad_i(arg1));
}

static double acos_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) > 1) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return acos(arg1);
   }
}

static double acosd_i(double arg1)
{
   return deg_i(acos_i(arg1));
}

static double cosh_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) > 70) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return cosh(arg1);
   }
}

static double tan_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return tan(arg1);
   }
}

static double tand_i(double arg1)
{
   return tan_i(rad_i(arg1));
}

static double atan_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return atan(arg1);
   }
}

static double atand_i(double arg1)
{
   return deg_i(atan_i(arg1));
}

static double tanh_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) > 70) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return tanh(arg1);
   }
}

static double atan2_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else {
      return atan2(arg1,arg2);
   }
}

static double atand2_i(double arg1, double arg2)
{
   return deg_i(atan2_i(arg1, arg2));
}

static double rad_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return arg1*0.017453292519943295769237;
   }
}

static double deg_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return arg1*57.295779513082320876798155;
   }
}

static double exp_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 > 70) {
      SETERR(ERARITH);
      return blankvalue;
   } else if (arg1 < -70) {
      return 0.0;
   } else {
      return exp(arg1);
   }
}

static double ln_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 > 0) {
      return log(arg1);
   } else {
      SETERR(ERARITH);
      return blankvalue;
   }
}

static double log_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 > 0) {
      return log10(arg1);
   } else {
      SETERR(ERARITH);
      return blankvalue;
   }
}

static double sqrt_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 < 0) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      return sqrt(arg1);
   }
}

static double abs_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return fabs(arg1);
   }
}

static double sinc_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (fabs(arg1) < 1.0e-30) {
      return 1.0;
   } else {
      return sin(arg1)/arg1;
   }
}

static double max_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else if (arg1 > arg2) {
      return arg1;
   } else {
      return arg2;
   }
}

static double min_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else if (arg1 < arg2) {
      return arg1;
   } else {
      return arg2;
   }
}

static double erf_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      double p  =  0.327591100;
      double a1 =  0.254829592;
      double a2 = -0.284496736;
      double a3 =  1.421413741;
      double a4 = -1.453152027;
      double a5 =  1.061405429;
      double t1 = 1.0 / ( 1.0 + p * fabs(arg1));
      double t2 = t1*t1, t3 = t1*t2, t4 = t1*t3, t5 = t4*t1;
      if (arg1 > 0.0) {
         return 1.0-(a1*t1+a2*t2+a3*t3+a4*t4+a5*t5)*exp(-arg1*arg1);
      } else {
         return (a1*t1+a2*t2+a3*t3+a4*t4+a5*t5)*exp(-arg1*arg1)-1.0;
      }
   }
}

static double erfc_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
      return 1.0-erf_i(arg1);
   }
}

static double mod_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else if (arg2 == 0.0) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
     int   xxx = arg1/arg2;
     return arg1-xxx*arg2;
   }
}

static double int_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
     int xxx = arg1;  /* this could be dangerous */
     return (double)xxx;
   }
}

static double nint_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else {
     int xxx = (fabs(arg1) + 0.5);  /* this could be dangerous */
     if (arg1<0) xxx = -xxx;
     return (double)xxx;
   }
}

static double sign_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 == 0.0) {
      return 0.0;
   } else if (arg1 > 0.0) {
      return  1.0;
   } else {
      return -1.0;
   }
}

static double ran_i()
{
   double xxx = rand();
   return (xxx+1.0) / ((double) RAND_MAX + 1.0);
}

static double ranu_i(double arg1, double arg2)
{
   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else {
      return arg1+ran_i()*(arg2-arg1);
   }
}

static double rang_i(double arg1, double arg2)
{
   static int oddran=0;

   if ((arg1 == blankvalue) || (arg2 == blankvalue)) {
      return blankvalue;
   } else {
     double val, r1, r2;
     r1 = ran_i();
     r2 = ran_i();
     if (oddran == 0) {
        val = sqrt(-2*log(r1))*cos(6.283185307179586476925286*r2); oddran = 1;
     } else {
        val = sqrt(-2*log(r1))*cos(6.283185307179586476925286*r2); oddran = 0;
     }
     val = arg1 + fabs(arg2) * val;
     return val;
   }
}

static double ranp_i(double arg1)
{
   if (arg1 == blankvalue) {
      return blankvalue;
   } else if (arg1 < 0) {
      SETERR(ERARITH);
      return blankvalue;
   } else {
      double val, cum, p, f;
      if (arg1 < 40) {
         int xxx = rang_i(arg1,sqrt(arg1))+0.5;
         val = xxx;
      } else {
         cum = exp(-arg1);
         p = cum;
         val = 0.0;
         f = ran_i();
         while ( f >= cum) {
            val = val + 1.0;
            p = p * arg1 / val;
            cum = cum + p;
         }
      }
     return val;
   }
}

/* -------------------------------------------------------------------------- */
/*                   L O G I C A L         F U N C T I O N S                  */
/* -------------------------------------------------------------------------- */
static double ifgt(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a>b?1.0:0.0;
}

static double iflt(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a<b?1.0:0.0;
}

static double ifge(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a>=b?1.0:0.0;
}

static double ifle(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a<=b?1.0:0.0;
}

static double ifeq(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a==b?1.0:0.0;
}

static double ifne(double a, double b)
{
   if (a==blankvalue || b==blankvalue) return blankvalue;
   return a!=b?1.0:0.0;
}

#<

#>            dcdscan.l
%{
/* dcdscan.l
                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

Scanner for numeric user input.
Author: J.P. Terlouw

                                 NOTICE

The current makefiles call lex only on hp9000s700 systems. Other
architectures compile dcdscan.c without calling lex.
*/

#include "stdlib.h"
#include "math.h"
#include "dcdcom.h"
#include "y.tab.h"                /* generated by yacc -d */
#include "varmgr.h"
#include "dcdscan.h"
#undef   input                    /* defined internally */
#undef   unput                    /* defined internally */
#undef   output                   /* defined internally */
#define  output(x)                /* no-op */
#define token(x) return x
#if defined(stdin)
#undef stdin
#endif
#if defined(stdout)
#undef stdout
#endif
#define stdin  NULL
#define stdout NULL

#if 0
static int yywrap(void);
#endif
static int identifier();
static int opchar();
static int number();
static char input(void);
static void unput(char);

static char  *bufptr;
static char  ustack[YYLMAX];
static int   stkptr=0;
static int   brakbal=0;
static int   scanmode=0;

%}

D     [0-9]
E     [DdEe][-+]?{D}+
OP    [\*\/\=\:\?]
a     [aA]
b     [bB]
c     [cC]
d     [dD]
e     [eE]
f     [fF]
g     [gG]
i     [iI]
l     [lL]
m     [mM]
r     [rR]
s     [sS]
t     [tT]

%START MATH LITERAL GDSLEV
%%
<LITERAL>[0-9a-zA-Z\/\._\-]+  { (void)identifier(); token(STRING); }
<GDSLEV>[ 0-9a-zA-Z\:\-]+     { (void)identifier(); token(STRING); }
<MATH>{D}+(\.{D}*)?({E})?     { token(number()); }
<MATH>\.{D}+({E})?            { token(number()); }
<MATH>{d}{e}{s}{c}{r}         { token(DESCR); }
<MATH>{i}{m}{a}{g}{e}         { token(IMAGE); }
<MATH>{t}{a}{b}{l}{e}         { token(TABLE); }
<MATH>{f}{i}{l}{e}            { token(TEXTFILE); }
<MATH>[A-Za-z][A-Za-z0-9]*    { token(identifier()); }
<MATH>[ ]*{OP}[ ]*            { token(opchar()); }
<MATH>[-]                     { token('-'); }
<MATH>[+]                     { token('+'); }
<MATH>[\(\[][ ]*              { brakbal++; token('('); }
<MATH>[ ]*[\)\]]              { brakbal--; token(')'); }
<MATH>[ ]*[\,][ ]*            { if (brakbal) token(','); else token(WHITE); }
<MATH>[ ]*"**"[ ]*            { token(POWER); }
<MATH>[ ]*"::"[ ]*            { token (REPEAT); }
<MATH>[ ]+                    { token(WHITE); }
[ ]*\n                        { token('\n');  }
.                             { token(LEXERR); }
%%

extern void dcdinit(char *text)
{
   bufptr = text;
   while (*bufptr==' ') bufptr++;
   stkptr = 0;
   brakbal= 0;
   BEGIN MATH;
}

extern void dcdmode(int x)
{
   /* ... symbolic modes would be nicer */
   switch (x) {
      case 1:  BEGIN LITERAL; break;
      case 2:  BEGIN GDSLEV;  break;
      default: BEGIN MATH;
   }
   scanmode = x;
}

static char input(void)
{
   if (stkptr) {
      return ustack[--stkptr];
   }
   if (!*(bufptr)) {
      return '\n';
   }
   return *(bufptr++);
}

static void unput(char c)
{
   if (stkptr<YYLMAX) {
      ustack[stkptr++] = c;
   }
}

static int identifier()
{
   yylval.valval = StrVal((char*)yytext);
   if (IsVar((char*)yytext)) return IDENTIFIER; else return STRING;
}

static int opchar()
{
   char *opstring=(char*)yytext;
   while (*opstring==' ') opstring++;
   return (int)*opstring;
}

static int number()
{
   int    i;
   double curval, f, frac, sumlog;
   int    tenp;
   char ch;
   
   yylval.valval.type   = DOUBLE;
   curval = 0.0;
   for (i=0; i<yyleng && isdigit(yytext[i]); i++) {
      curval = 10.0 * curval + yytext[i] - '0';
      if (curval>MAXFLOAT) return BADNUM;
   }
   if (yytext[i]=='.') {
      i++;
      f = 1.0;
      frac = 0;
      for (; i<yyleng && isdigit(yytext[i]); i++) {
         frac = 10.0 * frac + yytext[i] - '0';
         f = 10.0 * f;
         if ((frac > MAXFLOAT)||(f > MAXFLOAT)) return BADNUM;
      }
      curval = curval + frac/f;
   }
   ch = yytext[i];
   if ((curval!=0) && ((ch == 'E')||(ch == 'e')||(ch == 'D')||(ch == 'd'))) {
      i++;
      tenp = 1;
      frac = 0.0;
      if (yytext[i] == '+') {
         i++;
      } else if (yytext[i] == '-') {
         tenp = -tenp;
         i++;
      }
      for (; i<yyleng && isdigit(yytext[i]); i++) {
         frac = 10.0 * frac + yytext[i] - '0';
      }
      frac *= tenp;
      sumlog = log10(curval)+frac;
      if ((MINLOG < sumlog)&&(sumlog < MAXLOG)) {
         curval = curval * pow(10.0,(double) frac);
      } else return BADNUM;
   }
   yylval.valval.value = curval;
   return NUMBER;
}

#if 0
static int yywrap()
#else
extern int yywrap()
#endif
{
   return 1;
}
#<

#>            tekwindow.c
/* tekwindow.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It implements a Tektronix overlay window.
Author: J.P. Terlouw.
*/
#include "stddef.h"
#include "stdlib.h"
#include "string.h"
#include <fcntl.h>
#include <sys/file.h>
#include "keyboard.h"
#include "keysym.h"
#include "prompter.h"
#include "mainloop.h"
#include "thermescom.h"
#include "tekwindow.h"
#include <unistd.h>

#define TEKNAME "THERTEK"

typedef struct {
   char *name;      /* terminal type name */
   char *tekon;     /* sequence to switch Tektronix mode on */
   char *tekoff;    /* sequence to switch Tektronix mode off */
   int  backstore;  /* flag indicating whether Tek image is saved by terminal */
} *terminal, _terminal;

#define TBUFSIZE 256        /* buffer size for Tektronix file reads */
#ifdef	DELAY
#undef	DELAY
#endif

#define DELAY      1        /* number of seconds to wait if no data available */

extern int LINES, COLS;

static long kid, tid;
static int cread, fileid;
static int tfd=0;
static _terminal terms[]={
   { "NCSA",  "\033\014",  NULL,        0},
   { "LADA",  "\033[?38h", "\033[?38l", 1},
   { "XTERM", "\033[?38h", "\033\003",  1},
   { "TEST1", "\033[?38h", NULL      ,  1}, /* xterm w/o switch back */
   { "TEST2", "\033[?38h", "\033\003",  0}, /* xterm w/o backstore   */
   { NULL,    NULL,        NULL,        0}
};
static terminal term=NULL;
static int active=0;

static void deactivate(void);
static int  getchars(long,int,void*);
static int  waitkey(long,int,void*);
static void readtek(long,int,void*);
static void delay(long,void*);
static int  checkid(void);
static int  setterm(long,int,char*,void*);
static char wwwhelp[]="*hermes/tektronix.html";

/* ========================================================================== */
/*                                 TekWindow                                  */
/* -------------------------------------------------------------------------- */
/*  Activate the Tektronix overlay window.
 */
extern void TekWindow(void)
{
   char *tekfile;

   if (active) {
      deactivate();
      return;
   }
   if (!term) {
      SetPrompterHelp(
         SetPrompterError(
            Prompter("Terminal type?         ", "LADA", setterm, NULL, 0, 0),
            "Error: unknown terminal" ),
         wwwhelp
      );
      return;
   }
   if (!tfd) {
      fileid = 0;
      tekfile = getenv(TEKNAME);
      if (!tekfile) tekfile = TEKNAME;
      tfd = open(tekfile,O_RDONLY,000);
      if (tfd<0) {
         tfd = 0;
         FlashText(0,0," Cannot open plot file ",3);
         return;
      }
      cread = 0;
   }
   (void)fcntl(tfd,F_SETFL,O_NDELAY);
   SuspendScreen();                       /* clear screen and suppress output */
   active = 1;
   write(1,term->tekon,strlen(term->tekon));         /* switch Tektronix mode */
   (void)checkid();
   tid = InsertMessageProc(readtek,tfd,NULL);
   kid = InsertKeyboardProc(getchars,NULL);
}

/* ========================================================================== */
/*                              SelectTek                                     */
/* -------------------------------------------------------------------------- */
/*  Choose terminal.
 */
int SelectTek(char *name)
{
   terminal t;

   for (t=terms; t->name; t++) {
      if (!strcmp(name,t->name)) {
         term = t;
         return 0;
      }
   }
   return -1;
}

/* -------------------------------------------------------------------------- */
/*                               getchars                                     */
/* -------------------------------------------------------------------------- */
/*  Obtain keyboard characters. It only reacts to a limited number of
 *  characters. The rest is propagated.
 *  This keyboard handler stays at front of the list; it prevents
 *  rescheduling.
 */
static int getchars(long id, int c, void *arg)
{
   if (c==QRESCHED || c==XRESCHED) return 0;
   /* special characters for tekwindow */
   switch (c) {
      case CTRL_L: fileid = 0; (void)checkid(); return 0;  /* refresh image */
      case ESCBIT|'Z':
      case CXBIT|CTRL_C:
      case CTRL_Q: deactivate(); return 0;                 /* attempt to quit */
      case CXBIT|'U':
      case CXBIT|CTRL_U: return 0;    /* prevent overlay shell */
      case ESCBIT|'E':   return 0;    /* prevent keyword editor */
      case CXBIT|'E':
      case CXBIT|CTRL_E: return 0;    /* prevent text editor */
   }
   return c;
}

/* -------------------------------------------------------------------------- */
/*                               readtek                                      */
/* -------------------------------------------------------------------------- */
/*  obtain Tektronix instructions.
 */
static void readtek(long id, int fd, void *arg)
{
   char buffer[TBUFSIZE];
   int  nr;

   if ((nr=read(fd,buffer,TBUFSIZE))>0) {
      cread += nr;
      write(1,buffer,nr);                                /* write to terminal */
   } else {
      if (checkid()) {
         RemoveMessageProc(id);
         tid = InsertTimerProc(delay,DELAY,NULL); /* delay if still same file */
      }
   }
}

/* -------------------------------------------------------------------------- */
/*                               delay                                        */
/* -------------------------------------------------------------------------- */
/*  Delay when no data available
 */
static void delay(long id, void *arg)
{
   if (checkid()!=-1) {
      RemoveMessageProc(id);
      tid = InsertMessageProc(readtek,tfd,NULL);
   }
}

/* -------------------------------------------------------------------------- */
/*                               deactivate                                   */
/* -------------------------------------------------------------------------- */
/*  Clean up everything.
 */
static void deactivate(void)
{
   char manmsg[]="\r\n Manually switch to character mode, then press a key\n\r";

   RemoveMessageProc(tid);
   RemoveKeyboardProc(kid);
   if (term->tekoff) {
      write(1,term->tekoff,strlen(term->tekoff));    /* switch Tektronix mode */
      ResumeScreen();
      if (!term->backstore) {
         close(tfd);
         tfd = 0;
      }
   } else {
      write(1,"\015\035\033\014",4);                          /* clear screen */
      write(1,manmsg,sizeof(manmsg));
      close(tfd);
      tfd = 0;
      (void)InsertKeyboardProc(waitkey,NULL);
   }
   active = 0;
}

/* -------------------------------------------------------------------------- */
/*                                 checkid                                    */
/* -------------------------------------------------------------------------- */
/*  Check whether file has been rewritten.
 */
static int checkid(void)
{
   int newid;

   (void)lseek(tfd,0,L_SET);
   if (read(tfd,&newid,sizeof(int))!=sizeof(int)) {
      (void)lseek(tfd,0,L_SET);
      return -1;
   }
   if (newid==fileid) {
      (void)lseek(tfd,cread,L_SET);
      return 1;
   } else {
      fileid = newid;
      cread = sizeof(int);
      write(1,"\015\035\033\014",4);                          /* clear screen */
      return 0;
   }
}

/* -------------------------------------------------------------------------- */
/*                                 setterm                                    */
/* -------------------------------------------------------------------------- */
/*  Called by prompter activated by TekWindow to set terminal type.
 */
static int setterm(long id, int status, char *name, void *arg)
{
   if (status==PROMPT_CANCEL) return 0;            /* user cancelled prompter */
   StringUp(name);
   if (SelectTek(name)) return -1;
   TekWindow();
   return 0;
}

/* -------------------------------------------------------------------------- */
/*                                 waitkey                                    */
/* -------------------------------------------------------------------------- */
static int waitkey(long id, int c, void *arg)
{
   if (c==XRESCHED) return 0;
   RemoveKeyboardProc(id);
   ResumeScreen();
   return 0;
}
#<

#>            terminal.c
/* terminal.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It is responsible for the terminal settings
in tHermes.

Author: J.P. Terlouw.
*/

/*
 * WARNING: some system include files below are not ANSI C.
 *          Therefore this program MUST be compiled with the gcc
 *          -traditional switch. Failing to do so gives no compiler
 *          errors but an incorrect object!
 */

#include "stddef.h"
#include "terminal.h"
#include "unistd.h"
#if defined(__aix__) | defined(__sgi__) | defined(__linux__) | defined(__APPLE__)
#define USE_TERMIOS
#endif

#if defined(__convex__) && defined(__GNUC__)
#define _SYS_TERMIOS_H_
#endif

#if defined(USE_TERMIOS)
#if defined(__APPLE__)
#include <termios.h> 
#include <sys/ioctl.h>
#else
#include <termio.h>
#endif
#else
#include <fcntl.h>
#include <sgtty.h>
#endif

#if defined(USE_TERMIOS)
#if defined(__APPLE__)
static struct termios tt;
static struct termios ttorg;
#else
static struct termio tt;
static struct termio ttorg;
#endif
#else
static struct sgttyb tt;
static struct sgttyb ttorg;
#endif

#if defined(__mips__)
struct tchars otchars;        /* Saved terminal special character set */
struct tchars ntchars = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; /* nothing */
#endif


extern void Terminal(mode)
int mode;
{
   fsync(1);
   switch(mode) {
      case HERTERM:               /* set tHermes terminal characteristics */
#if defined(USE_TERMIOS)
#if defined(__APPLE__)
   ioctl(0,TIOCGETA,&ttorg);
#else
   ioctl(0,TCGETA,&ttorg);
#endif
   tt.c_iflag = 0;
   tt.c_oflag = 0;
   tt.c_cflag = ttorg.c_cflag;
   tt.c_lflag = 0;
#if !defined(__APPLE__)
   tt.c_line = ttorg.c_line;
#endif
   tt.c_cc[VMIN] = 1;
   tt.c_cc[VTIME] = 0;
#if defined(__APPLE__)
   ioctl(0,TIOCSETA,&tt);
#else
   ioctl(0,TCSETA,&tt);
#endif
#elif defined(__mips__)
   gtty(0, &ttorg);
   gtty(0, &tt);
   ioctl(0, TIOCGETC, &otchars);           /* Save old characters */
   tt.sg_flags |=  RAW;
   tt.sg_flags &= ~ECHO;
   stty(0, &tt);
   ioctl(0, TIOCSETC, &ntchars);           /* Place new character into K */
#else
   if (once) {
      once--;
   ioctl(0,TIOCGETP,&ttorg);               /* save current terminal char's   */
   }
   ioctl(0,TIOCGETP,&tt);
   tt.sg_flags |=  RAW;                    /* set terminal characteristics   */
   tt.sg_flags &= ~ECHO;
   ioctl(0,TIOCSETP,&tt);
#endif
      break;

      case PRVTERM:                       /* restore previous characteristics */
#if defined(USE_TERMIOS)
#if defined(__APPLE__)
      ioctl(0,TIOCGETA,&ttorg);
#else
      ioctl(0,TCSETA,&ttorg);
#endif
#elif defined(__mips__)
      stty(0, &ttorg);
      ioctl(0, TIOCSETC, &otchars);   /* Place old character into K */
#else
      ioctl(0,TIOCGETP,&tt);
      ioctl(0,TIOCSETP,&ttorg);         /* reset terminal characteristics */
#endif
   }
}  /* dit is dus het rommelkamertje van tHermes */
#<

#>            thermes.c
/* thermes.c
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands


Author: J.P. Terlouw.
*/

#include "signal.h"
#include "stdio.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "time.h"
#include <sys/time.h>
#include <sys/file.h>

#include "thermescom.h"

int NTASK=DNTASK;
extern int LINES,COLS;                      /* screen dimensions from Curses */
extern char **environ;
extern char *getcwd(char*, size_t);


int  open_log(int logwait);

/*
 * =================  Static routine declarations ============================
 */
                                                  /* Keyboard handlers */
static int KeyHandler(long,int,void*);
static int Esc(long,int,void*);
static int Ctrlx(long,int,void*);
static int ExitPrompt(long,int,void*);
                                                  /* Timer procedures */
static void ClockDisplay(long,Window);
static void UpdateKeyFile(long,void*);
static void Finish(long,TaskStatus,void*);
                                                  /* Message handlers */
static void TaskRequest(long,int,TaskStatus);
                                                  /* Prompter procedures */
static int  GoPage(long, int, char*, void*);
static int  ChangeDir(long, int, char*, void*);
static int  ChangePath(long, int, char*, void*);
static int  InitPrint(long, int, char*, void*);
                                                  /* Uca handlers */
static void UcaHandler(long,int,void*);
                                                  /* Misc. services   */
static void Intro(int);
static void KeyPrompt(TaskStatus);
static void CleanUp(TaskStatus);
static void HintReport(void);
static void isproc(TaskStatus);
/*
 * ================= Static variables global to this module ==================
 */
static struct _TaskStatus template;      /* task status block template        */
static TaskStatus tasklist;              /* task status block list head       */
static Window clockwin;                  /* wall clock display                */
static long kbid;                        /* lowest level keyboard handler id  */
static long ucaid;                       /* UCA keyboard handler id           */
static int tasksactive=0;                /* number of currently active tasks  */
static int taskspausing=0;               /* number of currently pausing tasks */

TaskStatus Template=&template;           /* make template globally known */

/* ========================================================================== */
/*                           M A I N   P R O G R A M                          */
/* -------------------------------------------------------------------------- */
int main(int argc, char **argv)
{
   Window statcol;
   int    i;

   TaskStatus task;
   static struct _Environment *env;       /* implement. dependent environment */


   strcpy(template.name,"(template)");           /* template default contents */
   template.isproc = isproc;
   template.errlev = 4;
   template.meslev = 1;
   template.devmask= DEVMASK;
   template.output_mode = NORMAL;
   template.status_mask = S_TERM;
   template.unhide = 0;
   getcwd(template.workdir, DIRLEN);

   if (open_log(0)) {
      fprintf(stderr,"Another session is already active "
                     "in this directory - exiting.\n");
      exit(1);
   }
   ReadContext(&template);                     /* 'user customization'       */
   env = NNew(NTASK,struct _Environment);
   Terminal(HERTERM);
   InitMain(NMESH);                            /* initialize mainloop module */
   InitKey(NKEYH);                             /* initialize keyboard module */
   InitWin();                                  /* initialize windows package */
   statcol = CreateWindow(NTASK,2);            /* column with dashes         */
   for (i=0; i<NTASK; i++) PutChar(statcol,i,0,'-');
   MapWindow(statcol,LINES-NUCA-NTASK,0);

   tasklist = inittask(NTASK,IPROC,NSUB);      /* initialize task manager    */
   i = 0;
   for (task=tasklist; task; task=task->next) {
      env[i].task = task;                         /* back pointer to t.s.b.  */
      env[i].tsa = CreateWindow(1,COLS-2);        /* task status area        */
      MapWindow(env[i].tsa,LINES-NUCA-NTASK+i,2);
      task->isdata = &env[i];                     /* pointer from t.s.b.     */
      i++;
   }

   clockwin = CreateWindow(1,5);                  /* clock display           */
   MapWindow(clockwin,LINES-NTASK-NUCA,COLS-7);

   (void)InsertTimerProc(ClockDisplay,30,clockwin); /* initialize clock      */
   (void)InsertTimerProc(UpdateKeyFile,300,NULL);   /* save 'macro'          */
   kbid = InsertKeyboardProc(KeyHandler,NULL);      /* init lowest keyhandler*/
   InitUca();                                  /* initialize Uca manager     */
   InitCmd();                                  /* initialize command stack   */
   InitCoa(LINES-NUCA-NTASK,COLS);             /* common output area (top!)  */
   ucaid= InsertUcaProc(UcaHandler,NULL);      /* init UCA   keyhandler      */
   if (MatchOn()) AidUca();
   if (!BeepOn()) SwitchBeep();
   InitAltKey();                        /* initialize 'alternative keyboard' */
   (void)Exec(Template,"$gip_exe/news mode=new");   /* show news             */
   if (StartBrowser()) InitWebHelp();               /* WWW help              */
   Intro(3);                                        /* opening display       */
   MainLoop();                                      /* ACTION!               */
}

/* ========================================================================== */
/*                                Intro                                       */
/* -------------------------------------------------------------------------- */

static void Intro(int delay)
{
   Window win;
static char intro[]=
"         Hermes version of "__DATE__ "           "
"                                                 "
"               W E L C O M E   T O               "
"                                                 "
"     [[[[[   [[[[[ [[[[[[[   [[[[[[  [[[   [[[   "
"    [[[  [[[  [[[   [[[ [[[ [[[  [[[ [[[   [[[   "
"   [[[    [[  [[[   [[[ [[[ [[[  [[[ [[[   [[[   "
"   [[[        [[[   [[[ [[[  [[[      [[[ [[[    "
"   [[[        [[[   [[[[[[    [[[[     [[[[[     "
"   [[[ [[[[[  [[[   [[[         [[[     [[[      "
"   [[[   [[[  [[[   [[[     [[[  [[[    [[[      "
"    [[[  [[[  [[[   [[[     [[[  [[[    [[[      "
"     [[[[ [[ [[[[[ [[[[[     [[[[[[    [[[[[     "
"                                                 "
"        http://www.astro.rug.nl/~gipsy/          "
"                                                 "
" (c)      Kapteyn Astronomical Institute         "
"     University of Groningen, The Netherlands.   "
" >> Press the TAB key for help at any moment! << "
;

   win = CreateWindow(19,49);
   win->framed = TRUE;                /* display should have borders         */
   free(win->backup);                 /* delete original window contents ... */
   win->private = FALSE;              /* ... and tell window package         */
   win->backup = intro;               /* use string intro as window contents */
   MapWindow(win,(LINES-win->ny)/2,(COLS-win->nx)/2);  /* display in center  */
   RefreshWindow(win);
#if 0
   InsertTimerProc(TimeDelWin,delay,win);  /* delete after 'delay' seconds   */
#else
   InsertKeyboardProc(KeyDelWin,win);      /*+++ will users like this?       */
#endif
}


/* ========================================================================== */
/*                                isproc                                      */
/* -------------------------------------------------------------------------- */
/*
 *  Implementation-specific procedure for ASCII terminal.
 *  This procedure is called at various occasions by the implementation
 *  independent routines, e.g. do_request, taskmgr. via a pointer to this
 *  procedure in the task status block.
 *  +++ There is however no good reason to use a procedure pointer here, so we
 *      may as well 'externalize' this module and get rid of the pointer.
 */
static void isproc(TaskStatus task)
{

   switch (task->state) {
      case IDLE    :    break;

      case WAITRUN :    task->proc->id_master =
                        InsertMessageProc(TaskRequest,
                                          task->proc->fd_master,task);
                        EnableTskOut(task);
                        put_psa(task,"WAITING TO BE RUN",S_STATUS);
                        if (!task->proc->next) tasksactive++;
                        /* WARNING: "task" changes meaning here ! */
                        for (task=tasklist; task; task=task->next) {
                           if (!task->proc) {
                              ClearWindow(((Environment)task->isdata)->tsa);
                              RefreshWindow(((Environment)task->isdata)->tsa);
                           }
                        }
                        break;

      case RUNNING :    put_psa(task,"RUNNING",S_STATUS);
                        if (task->prevstat==WAITINP) {
                           task->prevstat = RUNNING;
                           if (NameToTask(NameFromUca())==task) ClearUca();
                        }
                        break;

      case WAITINP :    if (task->proc->evdr_parent ||
                           (task->caller && task->caller->proc->fd_notify)) {
                           UserinpRequest req=(UserinpRequest)&task->request;
                           if (req->level == 1) req->level = 2;
                           if (!satisfy(task, req)) {
                              task->state = NODEFAULT;
                              aborttask(task);
                           }
                        } else {
                           KeyPrompt(task); Beep();
                           NotifyCaller(task, 1);
                        }
                        break;

      case WAITEDIT:    Beep(); break;

      case PAUSING :    Beep(); taskspausing++; break;

      case STARTFAIL:   if (!task->proc->next) {
                           put_psa(task,"COULD NOT BE STARTED",S_TERM);
                           Beep();
                        }
                        CleanUp(task); break;

      case FINISHED:    if (!task->proc->next) {
                           put_psa(task," +++ FINISHED +++",S_TERM);
                           Beep();
                        }
                        CleanUp(task); break;

      case CRASHED :    if (!task->proc->next) {
                           put_psa(task," CRASHED",S_TERM);
                           Beep();
                        }
                        HintReport();
                        CleanUp(task); break;

      case FATAL   :    Beep(); CleanUp(task); HintReport(); break;

      case ABORTED :    if (!task->proc->next) {
                           put_psa(task," USER ABORT",S_TERM);
                        }
                        CleanUp(task); break;

      case NODEFAULT :  if (task->caller) {             /* started by XEQ */
                           put_psa(task, " FORCED EXIT",S_TERM);
                        }
                        CleanUp(task); break;

      default      :    break;
   }
}
/* ========================================================================== */
/*                               HintReport                                   */
/* -------------------------------------------------------------------------- */
/*  HintReport flashes a message on the terminal screen containing a hint to
 *  use REPORT.
 */
static void HintReport(void)
{
   static int nhints=NHINTS;
   
   if (nhints) {
      FlashText(LINES-NUCA-NTASK-3,2,
               " Problems can be reported by running REPORT ",HINTTIME);
      nhints--;
   }
}

/* ========================================================================== */
/*                               TaskRequest                                  */
/* -------------------------------------------------------------------------- */
/*
 *  Message handler to be called by mainloop() at the occasion of
 *  a servant task request.
 */
static void TaskRequest(long id, int fd, TaskStatus task)
{
   if (get_request(task)) do_request(task);
}



/* ========================================================================== */
/*                                  CleanUp                                   */
/* -------------------------------------------------------------------------- */
/*
 *  Process termination clean-up routine.
 *  Called whenever a process terminates.
 */
static void CleanUp(TaskStatus task)
{

   if (!task->proc->next) {                        /* last process of task ? */
/*
 *   Trim the parameters to a minimum size. This is especially important
 *   for tasks with recall file parameters.
 *   +++ the next two statements should be moved to taskmgr.c as soon as
 *        xHermes uses the "standard" parmgr.
 */
      task->keep = 1;
      init_par(task);

/*
 *   XEQ-related stuff
 */
/* +++ to be moved to rundown() in taskmgr.c */
      if (task->callee) task->callee->caller = NULL;
      if (task->caller) {
         int result;
         switch (task->state) {
            case FINISHED:  result =  1; break;
            case STARTFAIL: result = -2; break;
            case FATAL:     result = -3; break;
            case CRASHED:   result = -4; break;
            case ABORTED:   result = -5; break;
            case NODEFAULT: result = -3; break; /* perhaps own result code? */
            default:        result = -999; break;
         }
         task->caller->callee = NULL;
         reply_send(task->caller,result);                    /* notify caller */
         (*task->caller->isproc)(task->caller);    /* reset "RUNNING" message */
      }
      tasksactive--;
   }
/*
 *   Remove file descriptor-related callbacks
 */
   RemoveMessageProc(task->proc->id_master);
   DisableTskOut(task);
}


/* ========================================================================== */
/*                               KeyPrompt                                    */
/* -------------------------------------------------------------------------- */
/*
 *  Routine to write a userinp prompt in the User Command Area.
 *  It only does so when the UCA is "free".
 *  The routine assumes that the prompt fits on the first line of the UCA.
 */
static void KeyPrompt(TaskStatus task)
{
   char prompt[80];

   if (task && (task->state==WAITINP)) {
      strcpy(prompt,task->name);
      strcat(prompt," ");
      strcat(prompt,((UserinpRequest)&task->request)->key);
      PutUca(prompt);
      if (task->rejected && *task->rejected) {
         char *badvalue=task->rejected;
         while (*badvalue) UcaType(*(badvalue++));
         UcaType(' ');
         FreeUca();
      }
   }
}

/* ========================================================================== */
/*                              KeyHandler                                    */
/* -------------------------------------------------------------------------- */
/*
 *  Routine called by keyboard() whenever there is a keyboard character to
 *  be processed. This routine is the first one to be inserted using
 *  InsertKeyboardProc() and consequently the last one to be called.
 */
static int KeyHandler(long id, int c, void *arg)
{
   static char wwwchdir[]="*hermes/dirpath.html#directory";
   static char wwwgopage[]="*hermes/COA.html#gopage";
   static char wwwprint[]="*hermes/COA.html#printing";
   static char wwwpath[]="*hermes/dirpath.html#path";

   switch (c) {
         case QRESCHED: return c;/* permit rescheduling but don't participate */

         case CTRL_X:                              /* activate CTRL-X handler */
                      InsertKeyboardProc(Ctrlx,NULL); break;

         case CTRL_Z: ShowPage(-1,1); break;     /* move one screen page back */

         case CTRL_I: InitHelp(); break;         /* pop up help display */


         case CTRL_V: ShowPage(+1,1); break;  /* move one screen page forward */

         case CTRL_L: RepairScreen(); break;          /* redraw entire screen */

         case CTRL_P: ShowPage(0,1); break;           /* toggle page mode     */

         case ESCBIT|'Z': case CXBIT|CTRL_C:
         case CTRL_Q: ExitPrompt(0,0,NULL); break;    /* Quit procedure   */

         case CTRL_R:                     /* search backwards in screen pages */
                      Search(-1);
                      break;

         case CTRL_S:                       /* search forward in screen pages */
                      Search(+1);
                      break;

         case ESC:                                   /* activate ESC handler */
                      InsertKeyboardProc(Esc,NULL); break;


                                              /* test */
         case 'A'|ESCBIT:
                      break;

                                              /* toggle bell */
         case 'B'|ESCBIT:
                      SwitchBeep(); break;

                                              /* change working directory */
                                              
         case 'C'|ESCBIT:                     /* calculator */
                      calculator(); break;

         case 'D'|ESCBIT:
                      SetPrompterHelp(
                      Prompter(
                      "Enter new working directory:                          ",
                      GetTaskDir(),ChangeDir,NULL,LINES-4,12),
                      wwwchdir);
                      break;

         case 'E'|ESCBIT:
                      if (NameFromUca() && *NameFromUca()) {
                         if (!NameToTask(NameFromUca()) &&
                             *GetUca()!='!') {
                            UcaType(CTRL_A);
                            UcaType('!');
                            UcaType(CTRL_E);
                         }
                         (void)EditKeys(NameFromUca());
                      } else (void)EditFile(NULL);
                      break;

         case 'G'|ESCBIT:
                      SetPrompterHelp(
                         Prompter("Page number:",NULL,GoPage,NULL,
                         LINES-4,COLS-18),
                         wwwgopage
                      );
                      break;

                                                       /* screen hard copy */
         case 'H'|ESCBIT:
                      SetPrompterHelp(
                         Prompter("Page range to print:",NULL,InitPrint,NULL,
                                   LINES-6,40),
                         wwwprint
                      );
                      break;
                                         /* switch minimal match on or off */
         case 'M'|ESCBIT:
                      AidUca(); break;
                      
                      
         case 'N'|ESCBIT:                /* expression display */
                      ShowExpr(); break;

                                                         /* select printer */
         case 'P'|ESCBIT:
                   SelectPrinter(); break;

                                                     /* task context setup */
         case 'S'|ESCBIT:
                      ContextMenu(NameToTask(NameFromUca())); break;

                                                              /* task path */
         case 'T'|ESCBIT:
                      SetPrompterHelp(
                         Prompter(
                         "Enter new task path:                                  ",
                         GetExecPath(),
                         ChangePath,NULL,LINES-6,15),
                         wwwpath
                      );
                      break;

         case 'K'|ESCBIT:
                      ShowKeys(NameFromUca()); break; /* keyword display  */

         case 'U'|ESCBIT:
                      ushell(); break;      /* integrated Unix shell (csh)*/
                      
         case 'X'|ESCBIT:
                      InitWebHelp(); break; /* toggle WWW help */

         case '<'|ESCBIT:
                      ShowPage(1,0); break;

         case '>'|ESCBIT:
                      ShowPage(9999,0); break;

         case CTRL_I|ESCBIT:
                      HelpKey(NULL); break;

         case CTRL_E|CXBIT: case 'E'|CXBIT:
                      (void)EditFile(NULL); break;              /* edit file */

         case CTRL_S|CXBIT: case 'S'|CXBIT:
                      (void)SetScroll(!SetScroll(0)); break; /* Toggle scroll */

         case CTRL_T|CXBIT: case 'T'|CXBIT:
                      TekWindow(); break;

         case 'U'|CXBIT: case CTRL_U|CXBIT:
                      oshell(); break;              /* overlay Unix shell */

         default:     Beep();

    }
    return 0;
}

/* ========================================================================== */
/*                                 InitPrint                                  */
/* -------------------------------------------------------------------------- */
/*
 *  InitPrint() makes a hardcopy of the range of screen pages specified in
 *  the argument 'pstring'.
 *  If pstring can be decoded successfully, PrintCoa is called to do the
 *  actual printing and 0 is returned; if pstring is in error, -1 is returned.
 *
 *  InitPrint is designed to be called as the action procedure of prompter.
 */
static int InitPrint(long id, int status, char *pstring, void *arg)
{
   int start = 0;
   int end   = 0;

   if (status==PROMPT_CANCEL) return 0;            /* user cancelled prompter */
   if (dcdrange(pstring,&start,&end)<0) {
      SetPrompterError(id,"- bad page range");
      return -1;
   } else {
      SetPrompterActive(id,"  printing...");
      PrintCoa(start,end);
      return 0;
   }
}


/* ========================================================================== */
/*                              UcaHandler                                    */
/* -------------------------------------------------------------------------- */
/*
 *  UCA callback procedure
 */
static void UcaHandler(long id, int reason, void *arg)
{
   static TaskStatus cycletask=NULL, spacetask=NULL, task;

/*
 * Initialization
 */
   if (!cycletask) {
      cycletask = tasklist;
      spacetask = tasklist;
   }

   switch (reason) {

      case UCA_ABORT:                                           /* abort task */
           task = NameToTask(NameFromUca());
           if (tasksactive==1 && task==&template) {
              for (task=tasklist; !task->proc; task=task->next);
           }
           if (task && task!=&template) {
              task->state = ABORTED;
              aborttask(task);
              ClearUca();
           } else Beep();
           break;

      case UCA_WAITGO:                            /* suspend or resume a task */
           task = NameToTask(NameFromUca());
           if (tasksactive==1 && task==&template) {
              for (task=tasklist; !task->proc; task=task->next);
           }
           if (task && task!=&template) {
              if (task->state == PAUSING) {
                 resume(task);
                 put_psa(task," RUNNING",S_STATUS);
                 taskspausing--;
              } else if (task->state != WAITINP) {
                 put_psa(task," PAUSING",S_STATUS);
                 suspend(task);
                 taskspausing++;
              }
           } else Beep();
           break;

         case UCA_RETURN:                            /* enter command        */
              {
                 char *cmd;
                 int cmdlen;

                 cmd = GetUca();
                 cmdlen = strlen(cmd);
                 if (cmdlen) {
                    cmd = Malloc(cmdlen+1);
                    strcpy(cmd,GetUca());
                    FreeUca();
                    if (Exec(&template,cmd)) StoreCmd(cmd);
                    Free(cmd);
                 }
                 break;
              }

         case UCA_CYCLE:    /* cycle through active tasks and put name in UCA */
              ClearUca();
              task=cycletask;
              do {
                 task = task->next;
                 if (!task) task=tasklist;
                 if (task->proc) {
                    char prompt[NAMLEN+2];
                    ClearUca();
                    strcpy(prompt,task->name);
                    strcat(prompt," ");
                    PutUca(prompt);
                    UcaFree();
                    break;
                 }
              } while (task!=cycletask);
              cycletask = task;
              break;

         case UCA_SPACE:   /* cycle through tasks waiting for input */
              task=spacetask;
              do {
                 task = task->next;
                 if (!task) task=tasklist;
                 if (task->state==WAITINP) {
                    KeyPrompt(task);
                    break;
                 }
              } while (task!=spacetask);
              spacetask = task;
              break;


      default: break;
   }
}

/* ========================================================================== */
/*                                 Esc                                        */
/* -------------------------------------------------------------------------- */
/*
 *  Esc handler. Interprets some ANSI escape sequences.
 *  Otherwise returns the input character with the ESCBIT
 *  bit set. (Keyboard handler)
 */
static int Esc(long id, int c, void *arg)
{
   static enum {INITIAL, ANSI, PGUP, PGDN} State = INITIAL;
   int result=0;

   if (c==XRESCHED) return c;                            /* propagate */

   /* simple finite State machine */
   switch (State) {
      case INITIAL:
        if (c=='[') {
           State = ANSI;                                 /* ANSI intro */
        } else {
            result = ESCBIT|(islower(c)?toupper(c):c);   /* other combination */
        }
        break;

      case ANSI:
        switch (c) {
           case '5': State = PGUP; break;                /* page-up intro */
           case '6': State = PGDN; break;                /* page-down intro */
           case 'A': result = UP; break;                 /* up-arrow */
           case 'B': result = DOWN; break;               /* down-arrow */
           case 'C': result = RIGHT; break;              /* right-arrow */
           case 'D': result = LEFT;  break;              /* left-arrow */
           default : result = -1;
        }
        break;

      case PGUP:
        result = (c=='~')?PAGEUP:-1; break;                /* page-up */

      case PGDN:
        result = (c=='~')?PAGEDOWN:-1; break;              /* page-down */
   }
   if (result) {
      RemoveKeyboardProc(id);
      State = INITIAL;
   }
   return result;
}

/* ========================================================================== */
/*                               Ctrlx                                        */
/* -------------------------------------------------------------------------- */
/*
 *  CTRL-X handler.
 *  Returns the input character with the ESCBIT bit set. (Keyboard handler)
 */
static int Ctrlx(long id, int c, void *arg)
{
   if (c==XRESCHED) return c;                            /* propagate */
   RemoveKeyboardProc(id);
   return CXBIT|(islower(c)?toupper(c):c);
}

/* ========================================================================== */
/*                               ChangeDir                                    */
/* -------------------------------------------------------------------------- */
/*
 *  Prompter procedure to change the default task directory.
 */
static int ChangeDir(long id, int status, char *result, void *arg)
{
   char msg[FILENAME_MAX+50];

   if (status==PROMPT_CANCEL) return 0;
   SetPrompterActive(id,"  checking...");
   if (SetTaskDir(result)) {
      SetPrompterError(id,"- invalid directory");
      return -1;
   }
   sprintf(msg,"<USER> Changed directory to: %s", GetTaskDir());
   put_coa(Template,msg);
   return 0;
}

/* ========================================================================== */
/*                               ChangePath                                   */
/* -------------------------------------------------------------------------- */
/*
 *  Prompter procedure to change the task search path.
 */
static int ChangePath(long id, int status, char *result, void *arg)
{
   char msg[31+100+1];

   if (status==PROMPT_CANCEL) return 0;
   if (SetExecPath(result)) {
      SetPrompterError(id,"- bad task path");
      return -1;
   }
   sprintf(msg,"<USER> Changed task path to: %s", GetExecPath());
   put_coa(Template,msg);
   return 0;
}

/* ========================================================================== */
/*                                  GoPage                                    */
/* -------------------------------------------------------------------------- */
/*
 *  Prompter procedure to go to specified page in screen file.
 */
static int GoPage(long id, int status, char *string, void *arg)
{
   int rel, page;

   if (status==PROMPT_CANCEL) return 0;            /* user cancelled prompter */
   rel = (*string=='+')||(*string=='-');
   page = (int)strtol(string,NULL,10);
   ShowPage(page,rel);
   return 0;
}

/* ========================================================================== */
/*                                ClockDisplay                                */
/* -------------------------------------------------------------------------- */
/*
 *  Timer procedure to maintain the clock display.
 */
static void ClockDisplay(long id, Window win)
{
   char cstring[6];

   struct timeval now;
   struct tm *tm;
   gettimeofday(&now,NULL);
   tm = localtime((time_t*)&now);
   sprintf(cstring,"%2d:%2.2d",tm->tm_hour,tm->tm_min);
   PutString(win,0,0,cstring);
   RefreshWindow(win);
}


/* ========================================================================== */
/*                                FlashText                                   */
/* -------------------------------------------------------------------------- */
/*
 *  Procedure to show a transient message. It disappears after the specified
 *  number of seconds or, if seconds is zero, when key is pressed.
 */
extern Window FlashText(int line, int col, char *text, int seconds)
{
   Window win;

   win = CreateWindow(3,strlen(text)+2);
   FrameWindow(win);
   PutString(win,1,1,text);
   MapWindow(win,line,col);
   if (seconds) InsertTimerProc(TimeDelWin,seconds,win);
   else         InsertKeyboardProc(KeyDelWin,win);
   return win;
}



/* ========================================================================== */
/*                               TimeDelWin                                   */
/* -------------------------------------------------------------------------- */
/*
 *  Timer procedure to delete a window.
 */
extern void TimeDelWin(long id, Window win)
{
   DeleteWindow(win);
   RemoveMessageProc(id);
}

/* ========================================================================== */
/*                                KeyDelWin                                   */
/* -------------------------------------------------------------------------- */
/*
 *  Keyboard handler to delete a window.
 */
extern int KeyDelWin(long id, int c, Window win)
{
   if (c==XRESCHED) return c;
   DeleteWindow(win);
   RemoveKeyboardProc(id);
   return c;
}

/* ========================================================================== */
/*                            UpdateKeyFile                                   */
/* -------------------------------------------------------------------------- */
/*
 *  Timer procedure to keep the "macro's" up to date.
 */
static void UpdateKeyFile(long id, void *arg)
{
   update_par();
}


/* ========================================================================== */
/*                                Exec                                        */
/* -------------------------------------------------------------------------- */
/*
 *  Process a command typed by the user or requested from a task using XEQ.
 */
extern TaskStatus Exec(TaskStatus caller, char *cmdlin)
{
   TaskStatus task=NULL ;
   char *taskname, *execname;
   char *key, *value;
   int  status;
   char message[80+1];
   char *c;
   int  charleft;
   long cmdid;

   status = ParseCmd(cmdlin);
   if (status) {
      if (status==NOTSKNAME) {
         status = EvalExpr(cmdlin);         /* treat as assignment statement */
         if (status && caller==&template) {
            err_uca(NULL,dcderrstr(status));
            return NULL;
         }
         if (caller==&template) {
            strcpy(message,"<USER> ");
            ClearUca();
         } else {
            strcpy(message,"<"); 
            strcat(message,caller->name); strcat(message,"> ");
         }
         /* --- ... duplicated code --- */
         charleft = strlen(cmdlin)-(80-strlen(message)-1);
         c = cmdlin + (80-strlen(message)-1);
         strncat(message,cmdlin,80-strlen(message)-1);
         put_coa(caller,message);
         put_log(caller,message);
         while (charleft>0) {
            strcpy(message,"       ");
            strncat(message,c,(80-8));
            charleft -= (80-8);
            c += (80-8);
            put_coa(caller,message);
            put_log(caller,message);
         }
         /* --------------------------- */
         if (status) return NULL;
         return (TaskStatus)-1;                    /* *negative* success! */
      }
      if (caller==&template) err_uca(NULL,CmdErrString(0));
      return NULL;
   }

   cmdid    = CmdGetInfo();
   taskname = CmdTaskName(cmdid);
   execname = CmdExecName(cmdid);
   if (!strcmp(taskname,"QUIT") ||
       !strcmp(taskname,"END")  ||
       !strcmp(taskname,"EXIT") ||
       !strcmp(taskname,"STOP")) {
      ExitPrompt(0,0,NULL);
      ClearUca();
      CmdDelete(cmdid);
      return NULL;
   }

   task     = NameToTask(taskname);
   if (!task) {
      for (task=tasklist; task; task=task->next) {
         if (!task->proc) {
            int i;
            TaskStatus next = task->next;            /* save fixed components */
            void *isdata = task->isdata;
            TaskStatus *xcallee=task->xcallee;

            *task        = *caller;                  /* inherit from caller   */
            task->next   = next;                     /* restore f. components */
            task->isdata = isdata;
            task->xcallee= xcallee;

            task->proc   = NULL;                     /* clear process pointer */
            task->state  = IDLE;                     /* clear process state   */
            task->keytree= NULL;                     /* clear userinp parms   */
            task->callee = NULL;                     /* clear callee          */
            task->xcaller= NULL;                     /* clear xcaller         */
            for (i=0; i<NTASK-1; i++) {
               task->xcallee[i] = NULL;              /* clear callees         */
            }
            task->reply  = NULL;                     /* reply buffer          */
            task->reply_in   = 0;                    /*  ,,     ,,   pointer  */
            task->reply_size = 0;                    /*  ,,     ,,   size     */
            task->stdo_in    = 0;                    /* stdout buffer pointer */
            task->keyfile = NULL;                    /* allpar file pointer   */
            task->ucamessage = NULL;                 /* UCA error message     */
            task->stamessage[0] = '\0';              /* status message        */
            break;
         }
      }
      if (!task) {
         if (caller==&template) err_uca(task,"NO FREE TASK SLOT");
         CmdDelete(cmdid);
         return NULL;
      }
      execname = FindExec(execname);
      if (!execname) {
         if (caller==&template) err_uca(task,"NO SUCH TASK AVAILABLE");
         CmdDelete(cmdid);
         return NULL;
      }
   } else {
      if (!CmdNkeys(cmdid) || caller!=&template) {  /* attempt to start task */
         if (caller==&template) err_uca(task,"ALREADY ACTIVE");
         CmdDelete(cmdid);
         return NULL;
      }
   }
   if (!task->proc) {
      task->keep  = CmdMacro(cmdid);                             /* macroflag */
      strcpy(task->name,taskname);
      init_par(task);
      if (!CmdMacro(cmdid)) {
         int status = rdefaults(task);
         if (status && caller==&template) {
            char errmess[80]="Default file error: ";
            strcat(errmess,CmdErrString(status));
            err_uca(task,errmess);
            CmdDelete(cmdid);
            return NULL;
         }
      }
   }
   if (caller==&template) {
      strcpy(message,"<USER> ");
      ClearUca();
   } else {
      strcpy(message,"<"); strcat(message,caller->name); strcat(message,"> ");
   }
   while (1) {
      CmdParam(cmdid,&key,&value);
      if (!key) break;
      insert_par(task, key, value, 0);
   }
   charleft = strlen(cmdlin)-(80-strlen(message)-1);
   c = cmdlin + (80-strlen(message)-1);
   strncat(message,cmdlin,80-strlen(message)-1);
   put_coa(caller,message);
   put_log(caller,message);
   while (charleft>0) {
      strcpy(message,"       ");
      strncat(message,c,(80-8));
      charleft -= (80-8);
      c += (80-8);
      put_coa(caller,message);
      put_log(caller,message);
   }
   if (!task->proc) {
      if (!strcmp(execname+strlen(execname)-4,".col")) {      /* cola script? */
         execname[strlen(execname)-4] = 0;                    /* strip ".col" */
         insert_par(task,"NAME=",execname,0);  /* name becomes input for cola */
         execname = "cola";                    /* and cola will be started    */
      }
      if (!starttask(task,execname)) {
         err_uca(NULL,"Unexpected problem! Cannot start task!");
         CmdDelete(cmdid);
         return NULL;
      }
      strcpy(task->name,taskname);   /* starttask may have changed it  */
      if (caller!=&template) {
         caller->callee = task;
         task->caller = caller;
         strcpy(message,"WAITING FOR "); strcat(message,task->name);
         put_psa(caller,message,S_STATUS);
      }
      if (CmdMacro(cmdid)) {
         if (!write_par(task->name,"keys.tmp")) {
            char *keys;
            int size;
            keys = FileContents("keys.tmp",&size);
            if (keys) {
               int i, col=0;

               put_log(caller," ");
               put_coa(caller," ");
               sprintf(message,"Current parameters of %s:",task->name);
               put_log(caller,message);
               put_coa(caller,message);
               for (i=0; i<size; i++) {
                  if (keys[i]=='\n') {
                     message[col++] = ' ';
                  } else {
                     message[col++] = keys[i];
                  }
                  if (col==1 && message[col-1]==' ') col = 0;
                  if (col==80 || (col>70 && message[col-1]==' ')) {
                     message[col] = '\0';
                     put_log(caller,message);
                     put_coa(caller,message);
                     col = 0;
                  }
               }
               if (col) {
                  message[col] = '\0';
                  put_log(caller,message);
                  put_coa(caller,message);
                  put_log(caller," ");
                  put_coa(caller," ");
               }
               free(keys);
            }
            remove("keys.tmp");
         }
      }
   }
   CmdDelete(cmdid);
   return task;
}


/* ========================================================================== */
/*                              ExitPrompt                                    */
/* -------------------------------------------------------------------------- */
/*
 *  Keyboard procedure to let the user acknowledge a quit request (^Q)
 */
static int ExitPrompt(long id, int c, void *arg)
{
   static Window win;

   if (!id) {
      (void)InsertKeyboardProc(ExitPrompt,NULL);
      win = CreateWindow(1,16);
      PutLine(win,0," Quit? Press: Y");
      win->framed = TRUE;
      MapWindow(win,LINES-1,0);
   } else {
      if (c==XRESCHED) return c;
      if (c=='Y' || c=='y') Finish(0,NULL,NULL);
      RemoveKeyboardProc(id);
      DeleteWindow(win);
   }
   return 0;
}



/* ========================================================================== */
/*                                  Finish                                    */
/* -------------------------------------------------------------------------- */
/*
 *  Task status callback which terminates the session as soon as there are no
 *  active tasks left.
 */
static void Finish(long id, TaskStatus dummy, void *arg)
{
   unsigned int sleep(unsigned int);

   TaskStatus task;
   Window win;
   char *waitmsg=" Wait for tasks to finish...";

   for (task=tasklist; task; task=task->next) {
      if (task->proc) break;
   }
   if (!task) {
      FinisNotify();
      FinisWin();
      sleep(1);           /* seems to help - why? */
      Terminal(PRVTERM);
      close_par();
      FinisCmd();
      RmAltKeySock();
      exit(0);
   }
   if (!id) {
      (void)InsertTaskProc(Finish,NULL);
      win = CreateWindow(1,strlen(waitmsg));
      PutString(win,0,0,waitmsg);
      win->framed = TRUE;
      MapWindow(win,LINES-1,0);
   }
}


/* ========================================================================== */
/*                              NameToTask                                    */
/* -------------------------------------------------------------------------- */
/*
 *   Translate a task name to a task status block pointer.
 */
extern TaskStatus NameToTask(char *name)
{
   TaskStatus task;

   if (!name || !*name) return &template;

   for (task=tasklist; task; task=task->next) {
      if (task->proc && !strcmp(name,task->name)) return task;
   }

   return NULL;
}


/* ========================================================================== */
/*                                KeyFromUca                                  */
/* -------------------------------------------------------------------------- */
/*
 *  Obtain a user input keyword from the User Command Area
 */
extern char *KeyFromUca(void)
{
   char *key, *value;
   static char result[KEYLEN+1];
   long cmdid;

   *result = '\0';
   if (!ParseCmd(GetUca())) {
      cmdid = CmdGetInfo();
      CmdParam(cmdid,&key, &value);
      if (key) strcpy(result,key);
      CmdDelete(cmdid);
   }
   return result;
}


/* ========================================================================== */
/*                                  put_psa                                   */
/* -------------------------------------------------------------------------- */
/*
 *  Put a message in a Task Status Area
 */
extern void put_psa(TaskStatus task, char* message, int mkind)
{
   char logline[LINLEN];
   Window tsa=((Environment)task->isdata)->tsa;

   strcpy(task->stamessage, message);
   NotifyCaller(task, 2);                     /* notify xeqcont() caller */
   PutLine(tsa,0,task->name);
   PutString(tsa,0,strlen(task->name)+2,message);
   RefreshWindow(tsa);
   if (task->status_mask & mkind) {
      strcpy(logline,"<STATUS>  ");
      strcat(logline,task->name);
      strcat(logline,"  ");
      strcat(logline,message);
      put_coa(task,logline);
      put_log(task,logline);
   }
}



/* ========================================================================== */
/*                                err_uca                                     */
/* -------------------------------------------------------------------------- */
/*
 *  Put an error message on top of the User Command Area
 */
extern void err_uca(TaskStatus task, char *message)
{
   static Window win=NULL;


   if (task && message && *message) {
      if (task->ucamessage) Delete(task->ucamessage);
      task->ucamessage = StrDup(message);
   }
   if (message && *message && UcaFree()) {
      if (task && task->proc &&
          (task->proc->fd_notify || task->proc->evdr_parent))
         return;                                             /* event-driven */
      KeyPrompt(task);
      win = CreateWindow(1,strlen(message)+2);
      win->framed = TRUE;
      PutString(win,0,1,message);
      MapWindow(win,LINES-1,COLS-strlen(message)-3);
      InsertKeyboardProc(KeyDelWin,win);
   }
}


/* ========================================================================== */
/*                              NameFromUca                                   */
/* -------------------------------------------------------------------------- */
/*  Obtain a task name from the User Command Area
 */
extern char *NameFromUca(void)
{
   return NameFromCmd(GetUca());
}

/* ========================================================================== */
/*                              NameFromCmd                                   */
/* -------------------------------------------------------------------------- */
/*  Obtain a task name from a command string
 */
extern char *NameFromCmd(char *buffer)
{
   long cmdid;
   static char result[NAMLEN+1];

   if (ParseCmd(buffer)) return "";
   else {
      cmdid  = CmdGetInfo();
      strcpy(result,CmdTaskName(cmdid));
      CmdDelete(cmdid);
      return result;
   }
}


/* ========================================================================== */
/*                                KeyFromCmd                                  */
/* -------------------------------------------------------------------------- */
/*  Obtain a user input keyword from a command string
 */
extern char *KeyFromCmd(char *buffer)
{
   char *key, *value;
   long cmdid;
   static char result[KEYLEN];

   *result = '\0';
   if (!ParseCmd(buffer)) {
      cmdid = CmdGetInfo();
      CmdParam(cmdid,&key, &value);
      if (key) strcpy(result,key);
      CmdDelete(cmdid);
   }
   return result;
}
#<

#>            ucamgr.c
/* ucamgr.c
                              COPYRIGHT (c) 1992
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It manages the User Command Area.
Author: J.P. Terlouw
*/

#include "stddef.h"
#include "string.h"
#include "thermescom.h"
#include "alloc.h"
#include "windows.h"
#include "keyboard.h"
#include "callback.h"
#include "webhelp.h"
#include "ucamgr.h"

typedef struct _Keyboard {
   Window uca;       /* User Command Area on terminal screen */
   char  *buffer;    /* command buffer */
   int   bufptr;     /* buffer pointer */
   int   nchars;     /* current size of command */
   int   buflen;     /* size of command buffer */
   bool  free;       /* indicates whether contents may be replaced by prompt */
} *Keyboard;

static struct _Keyboard keyboard;        /* user command structure            */
static CallbackChain cblist=NULL;
static bool fblflag=TRUE;
static bool prompted=FALSE;

static int  getchars(long,int,void*);
static void CallUcaProc(int);

extern int LINES, COLS;

/* ========================================================================== */
/*                               InitUca                                      */
/* -------------------------------------------------------------------------- */
/*   InitUca initializes the User Command Area.
 */
extern void InitUca(void)
{
   keyboard.uca = CreateWindow(NUCA,COLS-1);
   keyboard.buffer = (char*)Malloc(NUCA*(COLS-1));
   keyboard.buflen = NUCA*(COLS-1);
   ClearUca();
   MapWindow(keyboard.uca,LINES-NUCA,0);
   PutCursor(keyboard.uca,0,0);
   RefreshWindow(keyboard.uca);
   (void)InsertKeyboardProc(getchars,&keyboard);
}

/* ========================================================================== */
/*                               PutUca                                       */
/* -------------------------------------------------------------------------- */
/*
 *  Routine to write a  prompt in the User Command Area.
 *  It only does so when the UCA is "free".
 *  If the prompt does not fit in the UCA, the call is ignored.
 */
extern void PutUca(char *prompt)
{
   int i;
   Window uca=keyboard.uca;

   if (keyboard.free && strlen(prompt)<=keyboard.buflen) {
      if (keyboard.nchars) ClearUca();
      strcpy(keyboard.buffer,prompt);
      keyboard.nchars = strlen(keyboard.buffer);
      keyboard.bufptr = keyboard.nchars;
      keyboard.buffer[keyboard.bufptr] = 0;
      for (i=0; i<keyboard.nchars; i++) {
         PutChar(uca,i/uca->nx,i%uca->nx,keyboard.buffer[i]);
      }
      PutCursor(uca,i/uca->nx,i%uca->nx);
      RefreshWindow(keyboard.uca);
      CallUcaProc(UCA_PROMPT);
      prompted = TRUE;
   }
}

/* ========================================================================== */
/*                               UcaType                                      */
/* -------------------------------------------------------------------------- */
/*  external routine to enable other modules to 'type' in the User Command
 *  area.
 */
extern void UcaType(char c)
{
   (void)getchars(0,(int)c,&keyboard);
}

/* ========================================================================== */
/*                              ClearUca                                      */
/* -------------------------------------------------------------------------- */
/*  delete User Command Area contents
 */
extern void ClearUca(void)
{
   int i,j;
   Window uca=keyboard.uca;

   for (j=0; j<NUCA; j++) {
     for (i=0; i<uca->nx; i++) {
         PutChar(uca,j,i,'_');
      }
   }
   PutCursor(uca,0,0);
   RefreshWindow(uca);
   keyboard.nchars = 0;
   keyboard.bufptr = 0;
   keyboard.free = TRUE;
   CallUcaProc(UCA_CLEAR);
}

/* ========================================================================== */
/*                                FreeUca                                     */
/* -------------------------------------------------------------------------- */
/*  FreeUca() sets the 'free-status' of the UCA.
 */
extern void FreeUca(void)
{
   keyboard.free = TRUE;
}

/* ========================================================================== */
/*                                UcaFree                                     */
/* -------------------------------------------------------------------------- */
/*  UcaFree() returns the 'free-status' of the UCA.
 */
extern bool UcaFree(void)
{
   return keyboard.free;
}

/* ========================================================================== */
/*                                 GetUca                                     */
/* -------------------------------------------------------------------------- */
/*  GetUca() returns a pointer to the zero-terminated keyboard buffer string.
 */
extern char *GetUca(void)
{
   keyboard.buffer[keyboard.nchars] = 0;
   return keyboard.buffer;
}

/* ========================================================================== */
/*                                 GetUcaPtr                                     */
/* -------------------------------------------------------------------------- */
/*  GetUcaPtr() returns the buffer index corresponding to the next typing
 *  position.
 */
extern int GetUcaPtr()
{
   return keyboard.bufptr;
}

/* ========================================================================== */
/*                               InsertUcaProc                                */
/* -------------------------------------------------------------------------- */
/*  InsertUcaProc registers a procedure to be called whenever any of the
 *  reasons listed below is present.
 *
 *  The prototype of 'proc' is:
 *
 *     void proc(
 *               int  id,     // the identification of the registration
 *               int  reason, // the reason of the call
 *               void *arg);  // the argument 'arg' when calling InsertUcaProc
 *
 *       reason can be:  UCA_PROMPT    PutUca was called;
 *                       UCA_RETURN    a carriage return was typed;
 *                       UCA_ABORT     CTRL-C was typed;
 *                       UCA_WAITGO    CTRL-G or CTRL-W was typed;
 *                       UCA_CYCLE     CTRL-T was typed;
 *                       UCA_SPACE     a space was typed while UCA was free;
 *                       UCA_FBLANK    first blank on command line;
 *                       UCA_EQUAL     equals sign typed.
 */
extern long InsertUcaProc(UcaProc proc, void *arg)
{
   if (!cblist) cblist = NewCbChain();
   return (long)RegisterCb(cblist, (CallbackProc)proc, arg);
}

/* ========================================================================== */
/*                               RemoveUcaProc                                */
/* -------------------------------------------------------------------------- */
/*  RemoveUcaProc removes the registration of a procedure registered by
 *  calling InsertUcaProc.
 */
extern void RemoveUcaProc(long id)
{
   DeregisterCb(cblist, (CallbackId)id);
}

/* -------------------------------------------------------------------------- */
/*                                getchars                                    */
/* -------------------------------------------------------------------------- */
/*
 *  Keyboard handler associated with the User Command Area.
 */
static int getchars(long id, int c, void *arg)
{
   Keyboard kb=(Keyboard)arg;

   char *buffer=kb->buffer;
   Window uca=kb->uca;
   int i;

/*
 *  Rescheduling
 */
   if (c==QRESCHED) return YRESCHED;
   if (c==XRESCHED) {
      PutCursor(uca,kb->bufptr/uca->nx,kb->bufptr%uca->nx);
      RefreshWindow(uca);
      return 0;
   }
    
   if (c==WEBHELP && kb->nchars==0) {
      if (WebHelp("*hermes/UCA.html")) return c;
      return 0;
   }
/*
 *  Escape, CTRL-X sequence; other special character
 */
   switch (c) {
      case UP:
      case DOWN: {
         char *match=NULL;
         char *cmd;

         if (!kb->free && !prompted) match = GetUca();
         cmd = RecallCmd(match,c==UP?CMD_BACKWARD:CMD_FORWARD);
         if (cmd) {
            kb->free = TRUE;
            PutUca(cmd);
            if (strlen(cmd)) kb->free = FALSE;
         } else Beep();
         return 0;
      }

      case ESC|ESCBIT: {
         c = CTRL_T;
         break;
      }
   }


   if (c&(~0177)) return c;          /* remaining 'specials' propagate */

   fblflag = (fblflag || kb->free) && !prompted;
/*
 *  Control characters
 */
   if (c>0 && c<32) {
      switch (c) {
         case CTRL_A:             /* move to start of User Command Area (UCA) */
                      kb->bufptr = 0;
                      PutCursor(uca,0,0);
                      RefreshWindow(uca);
                      break;

         case CTRL_B:            /* move one position backwards in UCA        */
                      if (kb->bufptr>0) {
                         kb->bufptr--;
                         PutCursor(uca,kb->bufptr/uca->nx,kb->bufptr%uca->nx);
                         RefreshWindow(uca);
                      }
                      break;

         case CTRL_C:                                           /* abort task */
                  CallUcaProc(UCA_ABORT);
                  break;

         case CTRL_D:              /* forward delete one character in UCA     */
                  if (kb->bufptr<kb->nchars) {
                     for (i=kb->bufptr; i<kb->nchars; i++) {
                        *(buffer+i) = *(buffer+i+1);
                        PutChar(uca,i/uca->nx,i%uca->nx,*(buffer+i));
                     }
                     kb->nchars--;
                     PutChar(uca,kb->nchars/uca->nx,kb->nchars%uca->nx,'_');
                     RefreshWindow(uca);
                  }
                  kb->free = !kb->nchars;
                  break;

         case CTRL_E:                                   /* move to end of UCA */
                  kb->bufptr = kb->nchars;
                  PutCursor(uca,kb->bufptr/uca->nx,kb->bufptr%uca->nx);
                  RefreshWindow(uca);
                  break;

         case CTRL_F:                     /* move one position forward in UCA */
                  if (kb->bufptr<kb->nchars) {
                     kb->bufptr++;
                     PutCursor(uca,kb->bufptr/uca->nx,kb->bufptr%uca->nx);
                     RefreshWindow(uca);
                  }
                  break;

         case CTRL_G:
         case CTRL_W:                             /* suspend or resume a task */
                  CallUcaProc(UCA_WAITGO);
                  break;
                  
         case CTRL_I:                             /* pop up help display */
                  InitHelp();
                  break;

         case CTRL_K:                           /* forward delete rest of UCA */
                  if (kb->bufptr<kb->nchars) {
                     for (i=kb->bufptr; i<kb->nchars; i++) {
                        PutChar(uca,i/uca->nx,i%uca->nx,'_');
                     }
                     kb->nchars = kb->bufptr;
                     RefreshWindow(uca);
                     kb->free = !kb->nchars;
                  }
                  break;

         case CR:                                     /* enter command        */
                  if (kb->nchars) {
                     *(kb->buffer+kb->nchars) = '\0';
                     kb->free = TRUE;
                     CallUcaProc(UCA_RETURN);
                  }
                  break;

         case CTRL_T:       /* cycle through active tasks and put name in UCA */
                  CallUcaProc(UCA_CYCLE);
                  break;

         case CTRL_U: ClearUca(); ResetCmd(); break;   /* delete UCA contents */

         default: return c;
      }

/*
 *  delete key
 */
   } else if (c==DEL) {
      if (kb->bufptr>0) {
         for (i=kb->bufptr; i<kb->nchars; i++) {
            *(buffer+i-1) = *(buffer+i);
            PutChar(uca,(i-1)/uca->nx,(i-1)%uca->nx,*(buffer+i));
         }
         kb->nchars--;
         kb->bufptr--;
         PutChar(uca,kb->nchars/uca->nx,kb->nchars%uca->nx,'_');
         PutCursor(uca,kb->bufptr/uca->nx,kb->bufptr%uca->nx);
         kb->free = !kb->nchars;
      }
   } else if (c&CXBIT) {
      return c;
   } else {
      if (kb->nchars<kb->buflen) {
         for (i=kb->nchars; i>kb->bufptr; i--) {
            *(buffer+i) = *(buffer+i-1);
            PutChar(uca,i/uca->nx,i%uca->nx,*(buffer+i));
         }
         PutChar(uca,kb->bufptr/uca->nx,kb->bufptr%uca->nx,c);
         *(buffer+kb->bufptr) = c;
         kb->bufptr++;
         kb->nchars++;
         PutCursor(uca,kb->bufptr/uca->nx,kb->bufptr%uca->nx);
         RefreshWindow(uca);
         if (c==' ' && kb->free) {
            CallUcaProc(UCA_SPACE);
         } else kb->free = FALSE;
         if (!kb->free && c==' ' && fblflag && id) {
             CallUcaProc(UCA_FBLANK);
             fblflag = FALSE;
         }
         else if (!kb->free && (c=='=') && id) CallUcaProc(UCA_EQUAL);
      } else Beep();
   }
   RefreshWindow(uca);
   if (id) prompted = FALSE;         /* only human typist can reset this flag */
   return 0;
}

/* -------------------------------------------------------------------------- */
/*                                CallUcaProc                                 */
/* -------------------------------------------------------------------------- */
/*   CallUcaProc calls all registered UCA procedures
 */
static void CallUcaProc(int reason)
{
   InvokeCb(cblist,(void*)reason);
}
#<

#>            ushell.c
/* ushell.c
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to tHermes. It implements a pop-up terminal window
in which the Unix shell /bin/csh is run.

Author: J.P. Terlouw.
*/

#include <sys/types.h>

#include "ctype.h"
#include "string.h"
#include "signal.h"
#include "stdlib.h"

#ifdef	__aix__
#define	_BSD
#endif

#if 0  /* confusion about different forms of setpgrp() - declare implicitly */
#include <unistd.h>
#endif

#include <fcntl.h>

#include "thermescom.h"

#define NX   (COLS)    /* Window horizontal size */
#define NY   (LINES-6) /* Window vertical size   */
#define OVERLAP  5     /* # of lines to keep when scrolled */
#define CMBLEN  (COLS-3)     /* shell command buffer length */
#define IPBLEN 128     /* input buffer length */
#define SHELL  "/bin/csh"

typedef struct _UnixShell {
   Window   win;       /* Shell window           */
   int      output;    /* write socket           */
   int      input;     /* read socket            */
   long     kbid;      /* keyboard handler id    */
   char    *kbuf;      /* keyboard buffer        */
   int      bufptr;    /* keyboard bffr pointer  */
   long     uxid;      /* read socket handler id */
   int      pid;       /* Shell process id       */
   int      x;         /* cursor position ...    */
   int      y;         /* ... relative to frame  */
   int      log;       /* logging flag           */
   char     line[LINLEN+1];
   int      linptr;
} *UnixShell;

static int getkb(long id, int c, UnixShell us);
static void getux(long id, int fd, UnixShell us);
static void checkwindow(UnixShell);
static void deactivate(UnixShell);
static void logchar(UnixShell,char);
static void logstatus(UnixShell);

extern int LINES, COLS;
extern char **environ;
static int active=0;
static struct _TaskStatus tsb;  /* pseudo task status for put_coa, put_log */

/* ========================================================================== */
/*                                ushell                                      */
/* -------------------------------------------------------------------------- */
/*  ushell() initializes the tHermes Unix shell. At present, only one Unix
 *  shell is supported, though, if need comes, it should be easy to allow
 *  multiple instances.
 */
extern int ushell(void)
{
   UnixShell us;
   int fdi[2], fdo[2];

   if (active) return -1;           /* at present, allow only one Unix shell */

   us = Calloc(1,sizeof(struct _UnixShell));
   us->kbuf = Malloc(CMBLEN);
   pipe(fdi); pipe(fdo);        /* communication channels: tHermes <-> shell */
   us->input = fdi[0];
   us->output = fdo[1];
   (void)fcntl(us->input,F_SETFL,O_NDELAY);  /* obtain whatever is available */
   if ((us->pid=fork())==0) {
      /* --- here we are in the subprocess --- */
      int i;

      setpgrp(0,getpid());                  /* set process group to proc. id */
      dup2(fdo[0],0);                       /* standard input   */
      dup2(fdi[1],1);                       /* standard output  */
      dup2(fdi[1],2);                       /* standard error   */
      for (i=getdtablesize(); i>2; --i) close(i);  /* close all other files  */
      execle(SHELL,SHELL,"-i","-s",0,environ);  /* start the shell */
      exit(1);                              /* only reached if exec fails    */
   }
   /* --- this is the parent process --- */
   close(fdi[1]);                           /* close unused pipe ends */
   close(fdo[0]);
   tsb.devmask = SCREEN | LOGFILE;
   us->win = CreateWindow(NY,NX);
   FrameWindow(us->win);
   PutString(us->win,0,(us->win->nx-strlen(SHELL))/2,SHELL);
   logstatus(us);
   MapWindow(us->win,0,0);
   us->kbid = InsertKeyboardProc(getkb,us);           /* connect to keyboard */
   us->uxid = InsertMessageProc(getux,us->input,us);  /* connect to shell    */
   active++;
   return 0;
}

/*
 *  getkb() obtains a character from the keyboard module and performs the
 *  necessary processing.
 */
static int getkb(long id, int k, UnixShell us)
{
   char c=k;
   char *buffer=us->kbuf;

   /* -----------------------------------------------------------  scheduling */
   if (k==(ESCBIT|'U')) {
      if (us->win->mapped) {
         UnmapWindow(us->win);
         return XRESCHED;                              /* notify next handler */
      } else {
         MapWindow(us->win,-1,-1);
         RemoveKeyboardProc(id);                     /* move to front of list */
         us->kbid = InsertKeyboardProc(getkb,us);
         return 0;
      }
   }
   if (!us->win->mapped) return k;        /* not mapped: pretend not to exist */

   if (k==QRESCHED) return YRESCHED;                /* supports re-scheduling */
   if (k==XRESCHED) {
      MapWindow(us->win,-1,-1);                     /* map to old position    */
      checkwindow(us);
      return 0;
   }
   
   if (k==WEBHELP) {
      if (WebHelp("*hermes/unixshell.html#shell")) return k;
      return 0;
   }
   
   if (k!=('L'|ESCBIT))
   if (k&(~0177)) return k;  /* Escape, CTRL-X sequence; special "characters" */

   /* ------------------------------------------------------ normal operation */
   if (isprint(c)) {
      if (us->bufptr+1<CMBLEN) {
         buffer[us->bufptr++] = c;
         PutChar(us->win,us->y+1,++us->x,c);
      } else Beep();
   } else {
      switch (k) {
         case DEL:                                   /* back-delete character */
            if (us->bufptr) {
               us->bufptr--;
               us->x--;
               PutChar(us->win,us->y+1,us->x+1,' ');
            } else Beep();
            break;

         case CR:                                    /* enter command         */
            buffer[us->bufptr++] = '\n';
            write(us->output,buffer,us->bufptr);
            if (us->log) {
               int i;
               for (i=0; i<us->bufptr; i++) logchar(us,buffer[i]);
            }
            us->bufptr = 0;
            us->x = 0;
            us->y++;
            checkwindow(us);
            break;

         case CTRL_C:                               /* interrupt              */
            PutString(us->win,us->y+1,us->x+1,"^C");
            us->x += 2;
            kill(-us->pid,SIGINT);                  /* address process group! */
            break;

         case CTRL_U:                               /* delete whole line      */
            while (us->bufptr) {
               us->bufptr--;
               us->x--;
               PutChar(us->win,us->y+1,us->x+1,' ');
            }
            break;

         case 'L'|ESCBIT:
            us->log = !us->log;
            logstatus(us);
            break;

         default: return k;                   /* unknown character: propagate */
      }
   }
   checkwindow(us);
   return 0;
}

/*
 *  checkwindow() scrolls the window, if necessary.
 *  It also places the cursor.
 */
static void checkwindow(UnixShell us)
{
   if (us->y > us->win->ny-3) {
      ScrollWindow(us->win,us->win->ny-2-OVERLAP);
      us->y = OVERLAP;
   }
   if (us->win->mapped) {
      PutCursor(us->win,us->y+1,us->x+1);
      RefreshWindow(us->win);
   }
}

/*
 *  getux() obtains data from the pipe into which the shell writes and
 *  puts it in the terminal window. This function is a MainLoop message
 *  handler.
 */
static void getux(long id, int fd, UnixShell us)
{
   char c;
   char buffer[IPBLEN];
   int i,l;

   if ((l=read(fd,buffer,IPBLEN))>0) {
   for(i=0; i<l; i++) {
   c = buffer[i];
      switch (c) {

         case CTRL_H:
                  if (us->x) us->x--;
                  break;

         case TAB:
                  do {
                     PutChar(us->win,us->y+1,us->x+1,' ');
                     us->x++;
                     logchar(us,' ');
                  } while (us->x%8);
                  break;

         case '\n':
                  us->y++;
                  us->x = 0;
                  checkwindow(us);
                  logchar(us,'\n');
                  break;

         case CR:
                  us->x = 0;
                  break;

         default: if (isprint(c) && us->x<us->win->nx-2) {
                     PutChar(us->win,us->y+1,us->x+1,c);
                     logchar(us,c);
                     us->x++;
                  }
      }
   }
   checkwindow(us);
   } else deactivate(us);
}

/*
 *  deactivate() cleans up everything: files are closed, keyboard and
 *  message handlers are removed, memory is released.
 */
static void deactivate(UnixShell us)
{
   close(us->input);
   close(us->output);
   RemoveKeyboardProc(us->kbid);
   RemoveMessageProc(us->uxid);
   DeleteWindow(us->win);
   free(us->kbuf);
   free(us);
   active--;
}

/*
 *  logchar() handles the logging of commands typed and characters
 *  received from the shell.
 */
static void logchar(UnixShell us, char c)
{
   char *line;
   int  linptr;

   if (us->log) {
      line=us->line;
      linptr=us->linptr;
      if (linptr==0) {
         line[0] = ':';
         line[1] = ' ';
         linptr = 2;
      }
      if (c=='\n' || linptr==LINLEN) {
         line[linptr] = 0;
         put_coa(&tsb,line);
         put_log(&tsb,line);
         linptr = 2;
      } else {
         line[linptr++] = c;
      }
      us->linptr=linptr;
   }
}

/*
 *  logstatus() reports the current logging status in the window frame.
 */
static void logstatus(UnixShell us)
{
   if (us->log) PutString(us->win,us->win->ny-1,us->win->nx-15,"Logging: on ");
   else         PutString(us->win,us->win->ny-1,us->win->nx-15,"Logging: off");
}
#<

#>            varmgr.c
/* varmgr.c
                              COPYRIGHT (c) 1994
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to the Hermes user input parser.
It manages the parser's variables and constants.

Author: J.P. Terlouw
*/

#include <ctype.h>

#include "stdlib.h"
#include "string.h"
#include "dcdparse.h"
#include "varmgr.h"

#define MAXVAR  50               /* maximum number of variables an constants */
#define NAMLEN  20               /* maximum variable name length             */
#define MAXLST  10000            /* default maximum list length              */
#define MAXTEMP 20               /* maximum number of temporary lists        */


typedef struct {
   char  name[NAMLEN+1];
   Value value;
} var_entry;

static void  initialize(void);

static var_entry vars[MAXVAR];   /* variables */

static var_entry cons[]={
   {"BLANK", {DOUBLE, 0, 0, NULL}},                  /* MUST be first element */
   {"PI"   , {DOUBLE, 0, (double)3.141592653589793238462643, NULL}},
   {"C"    , {DOUBLE, 0, 2.997925e+8, NULL}},
   {"H"    , {DOUBLE, 0, 6.6256196e-34, NULL}},
   {"K"    , {DOUBLE, 0, 1.380622e-23, NULL}},
   {"G"    , {DOUBLE, 0, 6.6732e-11, NULL}},
   {"S"    , {DOUBLE, 0, 5.66961e-8, NULL}},
   {"M"    , {DOUBLE, 0, 1.99e30, NULL}},
   {"P"    , {DOUBLE, 0, 3.086e16, NULL}},
   {"INF"  , {DOUBLE, 0, MAXFLOAT*2, NULL}}
};                               /* constants */

static int    initialized=0;
static int    varstart;
static int    maxlst=MAXLST;
static double *temps[MAXTEMP];

/* ========================================================================== */
/*                                 IsVar                                      */
/* -------------------------------------------------------------------------- */
/*  IsVar() checks whether there exists a variable or constant with the
 *  specified name.
 */
extern int IsVar(char *name)
{
   int i;
   char iname[NAMLEN+1];
    
   if (!initialized) initialize();
   if (strlen(name)>NAMLEN) return 0;
   strcpy(iname,name);
   for (i=0; iname[i]; i++) iname[i] = toupper(iname[i]);
   for (i=0; i<MAXVAR; i++) if (!strcmp(iname,vars[i].name)) return 1;
   return 0;
}

/* ========================================================================== */
/*                                 GetVar                                     */
/* -------------------------------------------------------------------------- */
/*  GetVar() obtains the value of a variable or constant with the specified
 *  name.
 */
extern Value GetVar(char *name)
{
   Value result;
   int   i, j;
    
   char iname[NAMLEN+1];
    
   if (!initialized) initialize();
   if (strlen(name)>NAMLEN) return StrVal(name);
   strcpy(iname,name);
   for (i=0; iname[i]; i++) iname[i] = toupper(iname[i]);
   for (i=0; i<MAXVAR; i++) if (!strcmp(iname,vars[i].name)) {
      result = vars[i].value;
      if (result.type==LIST) {
         result.values = NNew(result.num,double);
         for (j=0; j<result.num; j++) result.values[j]=vars[i].value.values[j];
      }
      return result;
   }
   return StrVal(name);
}
   
/* ========================================================================== */
/*                                 PutVar                                     */
/* -------------------------------------------------------------------------- */
/*  PutVar() assigns a value to a new or existing variable. Assignment to
 *  a constant is an error. The function returns a result code. Zero for
 *  success, non-zero for an error.
 */
extern int PutVar(char *name, Value val)
{
   int i, item=0;
    
   char iname[NAMLEN+1];
    
   if (!initialized) initialize();
   if (strlen(name)>NAMLEN) return VARLONG;
   strcpy(iname,name);
   for (i=0; iname[i]; i++) iname[i] = toupper(iname[i]);
   for (i=0; i<MAXVAR; i++) if (!strcmp(iname,vars[i].name)) {
      if (i<varstart) return VARCASS;
      if (vars[i].value.type==LIST) Delete(vars[i].value.values);
      item = i;
      break;
   }
   if (!item) for (i=0; i<MAXVAR; i++) if (!vars[i].name[0]) {
      strcpy(vars[i].name,iname);
      item = i;
      break;
   }
   if (item) {
      vars[item].value = val;
      if (val.type==LIST) {
         vars[item].value.values = NNew(val.num,double);
         for (i=0; i<val.num; i++) vars[item].value.values[i] = val.values[i];
      }
      if (val.type==DOUBLE && val.value==cons[0].value.value) 
         vars[item].name[0] = 0; /* Delete variable if blank assigned to it */
      return 0;
   }
   return VARFULL;
}

/* ========================================================================== */
/*                                 StrVal                                     */
/* -------------------------------------------------------------------------- */
/*  StrVal() converts a character string to a TEXT variable.
 */
extern Value StrVal(char *name)
{
   Value result;
    
   result = NewList(strlen(name)/sizeof(double)+1);
   if (result.type==LIST) {
      result.type = TEXT;
      strcpy((char*)result.values,name);
   }
   return result;
}

/* ========================================================================== */
/*                                 FreeVar                                    */
/* -------------------------------------------------------------------------- */
/*  FreeVar() released any dynamically allocated space from the specified
 *  value structure.
 */
extern void FreeVar(Value *val)
{
   int i;
   
   if ((val->type==LIST || val->type==TEXT) && val->values) {
      for (i=0; i<MAXTEMP; i++) if (temps[i]==val->values) {
         temps[i] = NULL;
         break;
      }
      Delete(val->values);
   }
   val->num  = 0;
   val->type = NONE;
}


/* ========================================================================== */
/*                                 CleanVar                                   */
/* -------------------------------------------------------------------------- */
/*  CleanVar() cleans up any left-over temporaries.
 */
extern void CleanVar(void)
{
   int i;
    
   for (i=0; i<MAXTEMP; i++) {
      if (temps[i]) {
         Delete(temps[i]);
      }
   }
}
/* ========================================================================== */
/*                                 NewList                                    */
/* -------------------------------------------------------------------------- */
/*  NewList() creates a LIST variable of the specified size.
 *  It registers the values pointer in the temporary list table.
 *  This table is used for garbage collection.
 */
extern Value NewList(int size)
{
   int i;
   Value result={ NONE, 0, 0.0, NULL};
     
   if (size<=maxlst) {
      for (i=0; i<MAXTEMP; i++) if (!temps[i]) {
         temps[i] = NNew(size,double);
         break;
      }
      if (i<MAXTEMP) {
         result.type = LIST;
         result.num  = size;
         result.values = temps[i];
      } else dcderror(TMPOVF);
   } else {
      dcderror(BUFOVF);
   }
   return result;
}

/* ========================================================================== */
/*                                 MaxList                                    */
/* -------------------------------------------------------------------------- */
/*  MaxList() temporily increases the maximum list size to the specified
 *  value.
 */
extern void MaxList(int size)
{
   if (size>MAXLST) maxlst = size; else maxlst = MAXLST;
}

/* -------------------------------------------------------------------------- */
/*                                 initialize                                 */
/* -------------------------------------------------------------------------- */
/*  initialize() initializes the variable manager.
 *  It performs three functions:
 *   - initialize the BLANK variable;
 *   - initialize the constants;
 *   - initialize the temporaries table.
 */
static void initialize(void)
{
   int i;
   union {
      char   bb[sizeof(double)];
      double dd;
   } blankvalue;
   
   for (i=0; i<sizeof(double); i++) blankvalue.bb[i] = 0x77;
   cons[0].value.value = blankvalue.dd;
    
   varstart = sizeof(cons)/sizeof(var_entry);
   for (i=0; i<varstart; i++) vars[i]=cons[i];
   for (i=0; i<MAXTEMP; i++) temps[i] = NULL;
   initialized = 1;
}
#<

#>            webhelp.c
/* webhelp.c
                              COPYRIGHT (c) 1995
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands

This module is private to Hermes. It handles the tHermes help display by

WWW browsers.

Author: J.P. Terlouw.
*/

#include "stddef.h"
#include <sys/file.h>
#include <unistd.h>
#ifndef O_CREAT   
#include <fcntl.h>
#endif
#include "signal.h"
#include "stdlib.h"
#include "string.h"
#include "stdio.h"

#include "thermescom.h"
#include "windows.h"
#include "webhelp.h"

#define GIPSYHOME "http://www.astro.rug.nl/~gipsy/"
#define MAXARGS   5

char browscmd[255];
static enum {UNKNOWN, MOSAIC, NETSCAPE, ARENA} browser=UNKNOWN;
static char browsex[255]="";
static char *args[MAXARGS+5];
static int  pid=0;
static char mofile[80];
static char home[80]; 

/* ========================================================================== */
/*                               InitWebHelp                                  */
/* -------------------------------------------------------------------------- */
/*  InitWebHelp initializes the module.
 *  It starts the WWW browser specified in the environment variable WEB_BROWSER.
 *  The home page is the local file $gip_htm/index.html or, if the
 *  environment variable gip_htm is undefined, the gipsy home page at the
 *  Kapteyn Astronomical Institute.
 */
extern void InitWebHelp()
{
   char homepage[255];
   int  remote=0;
   static int nargs;
  
   if (pid) {
      if (browser==MOSAIC) remove(mofile);
      kill(pid,SIGKILL);
      pid = 0;
      browser = UNKNOWN;
   } else {
      if (WebBrowser()) {
         strcpy(browscmd,WebBrowser());
      } else if (getenv("WEB_BROWSER")) {
         strcpy(browscmd,getenv("WEB_BROWSER"));
      }
      if (*browscmd) {
         strcpy(browsex,strtok(browscmd," "));
         if (strstr(browsex,"osaic")) {
            browser = MOSAIC;
         } else if (strstr(browsex,"etscape")||strstr(browsex,"irefox")) {
            browser = NETSCAPE;
         } else if (strstr(browsex,"rena")) {
            browser = ARENA;
         }
         if (!args[0]) {
            args[0] = "wwwhelp";
            for (nargs=1; nargs<MAXARGS; nargs++) {
               char *currtok=strtok(NULL," ");
               if (!currtok) break;
               args[nargs] = StrDup(currtok);
            }
         }
         if (getenv("gip_htm")) {
            char filename[80];
            int fd;
            sprintf(filename,"%s/index.html",getenv("gip_htm"));
            fd = open(filename,O_RDONLY,000);
            if (fd<0) {
               strcpy(home,GIPSYHOME);
               remote = 1;
            } else {
               close(fd);
               sprintf(home,"file://localhost%s/",getenv("gip_htm"));
            }
         } else {
            strcpy(home,GIPSYHOME);
            remote = 1;
         }
         sprintf(homepage,"%sindex.html",home);
         pid = fork();
         if (!pid) {
            /* child branch */
            int i;
             
            for (i=0; i<64; i++) {
               close(i);
            }
            switch (browser) {
               case MOSAIC: {
                  signal(SIGUSR1, SIG_IGN);
                  args[nargs++] = "-home";
                  args[nargs++] = homepage;
                  args[nargs++] = "-ngh";
                  break;
               }
               case NETSCAPE:
                  args[nargs++] = homepage;
                  break;
               case ARENA: {
                  args[nargs++] = homepage;
                  break;
               }
               default: {
                  args[nargs++] = homepage;
                  break;
               }
            }
            args[nargs++] = NULL;
            (void)execv(browsex, args);
            _exit(-1);                            /* only in case of failure */
         }
         if (pid<0) {
            (void)FlashText(0, 0, " Cannot start WWW browser ",3);
            Beep();
         } else {
            if (remote)
               (void)FlashText(0, 0, " ... Access via Internet ",10);
            (void)FlashText(0, 0, " WWW browser activated... ",3);
         }
      } else {
         (void)FlashText(0, 0, " Cannot find WWW browser ",3);
         Beep();
      }
   }
}

/* ========================================================================== */
/*                                 WebHelp                                    */
/* -------------------------------------------------------------------------- */
/*  WebHelp() attempts to display the specified file in the WWW browser.
 *  On success, 0 is returned; on failure -1.
 *  A plain file name implies a file on the local host; a name prefixed
 *  by an asterisk is a document relative to the homepage.
 */
extern int WebHelp(char *name)
{
   char page[255];

   if (!pid) return -1;                                        /* no browser */

   if (browser!=NETSCAPE && kill(pid, 0)) {
      if (browser==MOSAIC) remove(mofile);
      pid = 0;
      return -1;                                         /* deceased browser */
   }
   
   if (*name=='*') {
      name++;
      sprintf(page,"%s%s",home,name);
   } else {
      sprintf(page,"file://localhost%s",name);
   }
   switch (browser) {
      case MOSAIC: {
         char   command[120];
         int    fd;
         
         sprintf(mofile,"/tmp/Mosaic.%d",pid);
         sprintf(command,"goto\n%s\n",page);
         fd = open(mofile, O_RDWR|O_CREAT, 0666);
         write(fd,command,strlen(command));
         close(fd);
         if (!kill(pid,SIGUSR1)) return 0;
         remove(mofile);
         break;
      }
      case NETSCAPE: {
         char command[255];
          
         sprintf(command,"%s -remote 'openURL(%s)' >/dev/null",
                          browsex,            page );
         (void)system(command);
         return 0;
         break;
      }
      default: break;
   }
   pid = 0;
   return -1;
}
#<

#>            hermes.dc1
Document:     hermes

Purpose:      master control program for GIPSY.

Category:     USER-INTERFACE

File:         hermes.src

Author:       J.P. Terlouw

Description:  There currenly are two versions:
	      - An interactive versions for ASCII terminals, named thermes;
              - A non-interactive version for batch work: nhermes.

See also:     thermes.dc1 nhermes.dc1

Updates:      Fri Sep 13, 1991: JPT, document created.
	      Fri Jan 31, 1992: MAMR, added nhermes.
              .
              .
              .
              Jun 22, 1993: JPT, Silicon Graphics support added.
              Jul 26, 1993: KGB, Solaris and DEC alpha support added.
              Sep 27, 1993: JPT, implemented NTASK= keyword in hermes.def.
              Oct 19, 1993: JPT, removed buffer length limitation.
              Nov  9, 1993: JPT, improved stdout and stderr output handling
              Nov 16, 1993: JPT, more stdout and stderr output improvements
              Nov 17, 1993: JPT, eliminated bug related to NTASK= keyword
              Nov 23, 1993: JPT, report hint in tHermes
              Nov 25, 1993: JPT, improved task EDITFILE interaction in tHermes
              Dec  7, 1993: JPT, prevent 2nd interactive session in 1 directory
              Dec  8, 1993: JPT, allow environment variables task directories
              Jan  4, 1994: JPT, implemented LISTCTRL function
              Jan 31, 1994: JPT, use SIGTSTP instead of SIGSTOP to supend tasks
              Feb 15, 1994: JPT, changes for Solaris 2.3
              Feb 17, 1994: JPT, fixed leading blanks bug in parmgr.c
              Feb 24, 1994: JPT, removed RAWPAR, added ALLPAR function.
              Jun  9, 1994: JPT, removed xhermes; added new user input parser.
              Jul   , 1994: JPT, added expression logging and calculator.
              Aug  1, 1994: JPT, fixed bug in ALLPAR file closing.
              Mar 31, 1995: JPT, fixed bug in dcdparse.y (e.g. 0**1 failed).
              Apr 28, 1995: JPT, fixed bug parmgr.c (quoted text containing ';')
              May  2, 1995: JPT, improved back-quote handling for user input
              Jul  4, 1995: JPT, thermes extensions (a.o. WWW browser support)
              Aug 25, 1995: JPT, more robust check for local html presence
              Aug 31, 1995: JPT, help: defined html context for empty UCA
              Oct 17, 1995: JPT, unlimited file size for file input function
              Jan 25, 1996: JPT, fixed -1 success check in xeq() (do_request.c)
              Apr 12, 1996: JPT, workaround for bug in HP-UX 10 lex
              Sep  2, 1996: JPT, KGB's modifications (a.o. suni386)
              Dec  5, 1996: JPT, check file's execute permission (execpath.c)
              Dec 17, 1996: JPT, change for new Linux (thermes.make)
              Feb 11, 1997: JPT, handle blanks correctly (dcdgds.c)
              Apr  8, 1997: JPT, event notification feature added (do_request.c)
              Jun  2, 1997: JPT, broken pipe bug fix (taskmgr.c)
              Jun  3, 1997: JPT, bad proc pointer in pseudo task status block
                                 caused NotifyTask to fail (parmgr.c)
              Jul 22, 1997: JPT, temporary and partial fix for rejected
                                 keywords in connection with event notification.
              Aug  1, 1997: JPT, array bounds error 'fixed'; nhermes now
                                 supports rejected keywords (event notifict'n).
              Aug  7, 1997: JPT, buffer allocation fixed (hermesipc.c)
              Sep 16, 1997: JPT, fixed bug in USERTEXT code (do_request.c);
                                 removed file descriptor leak (taskmgr.c).
              Nov  7, 1997: JPT, allow tabs as separator in files read
                                 by the file input function. (dcdfile.c)
              Dec 12, 1997: JPT, implemented usertext default code -1
              Jun  8, 1998: JPT, do not abort event-driven tasks in case of
                                 deputy task failure.
              Jul  1, 1998: JPT, implemented SAVEPAR function
              Aug 11, 1998: JPT, fixed task path bug.
              Oct  1, 1998: JPT, by default do not start WWW browser
              Jan 29, 1999: JPT, small changes for Linux 2.0
              Jun 24, 1999: JPT, underflow in exp() is no error anymore, but
                                 gives a zero result (dcdparse.y)
              Jul  2, 1999: JPT, modified thermes.make
              Nov 16, 1999: JPT, nhermes also reports current "macro" parameters
              Nov 23, 1999: JPT, nhermes keyword file write bug fixed
              Mar 16, 2000: JPT, allow BLANKs in files read by the file
                                 input function. (dcdfile.c)
              Jul 21, 2000: JPT, blank lines in files read by the file input
                                 function considered to be comments. (dcdfile.c)
              Aug 25, 2000: JPT, remove keyword subtitutions after deputy call.
              Oct  3, 2000: JPT, limit log file sync to once every minute.
              Dec  7, 2000: JPT, max directory and path length to FILENAME_MAX.
              Jan 11, 2001: JPT, extended WKEY functionality.
              Mar 29, 2001: JPT, modified log file locking. (put_log.c)
              May  8, 2001: JPT, improved WKEY synchronization.
              Jun 12, 2001: JPT, enhanced default file functionality.
              Sep 25, 2003: JPT, fixed keyword file read bug.
              Apr 10, 2006: JPT, interfaced Firefox to web help.
              May  2, 2007: JPT, included code for Apple Mac support.
              Aug 27, 2007: JPT, improved child process handling. (taskmgr.c)
              Mar 10, 2009: JPT, changed institute name in splash screen
                                 and increased buffer space for status message.
              Jun 16, 2009: JPT, cleaned up declarations and code and replaced
                                 writev() by sock_writev() (hermesipc.c)
              Jul 15, 2009: JPT, fix screen page number>9999 crashes (coamgr.c)
              Aug 11, 2009: JPT, renamed getline to GetLine.
              Jun 10, 2010: JPT, fixed precision bug (file function: dcdfile.c)
              Apr 14, 2011: JPT, implemented notification to caller task
                                 about about xeqcont-called task's actions.
              Apr 18, 2011: JPT, implemented aborttask function.
              Apr 20, 2011: JPT, nhermes: improved caller task notification
                                 and optional screen log file.
              Jun  6, 2011: JPT, do not take directories as tasks (execpath.c)
              May 18, 2012: JPT, allow xeqcont to start independent tasks.
              Aug  2, 2012: JPT, fixed crashes due to calls to reject() from
                                 tasks started by an event-driven task.
              Oct  1, 2012: JPT, fixed crash when supplied user input keyword's
                                 length was exactly 20 (parse.l, parse.c)
              Oct 15, 2011: JPT, fixed bug in error reporting (nhermes.c)
              Dec 26, 2011: JPT, notify event-driven tasks about recall file
                                 problems (cannot open, range error: parmgr.c)
#<

#>            nhermes.dc1
Document:       nhermes

Purpose:        GIPSY master control program for a non interactive environment

Category:       USER-INTERFACE

File:           hermes.src

Author:         M.A.M. Ruiter

Use:            nhermes [ -l<log file name> ] [ -t<minutes> ] { <user command> }

		You should give one or more user commands to nhermes.
		The -l and -t flags are optional.

		<user command> can be a cola script or a name of a servant task
		with their parameters.
		The name of the servant task or the cola script with their
		parameters should be suitable quoted or escaped to keep it
		together (e.g. between double quotes).
		A cola script file should have the extention .col as specified
		in the documentation of the application program COLA.
		You may give the name of the cola script with or without the
		extention .col to nhermes.

		The options may appear in any order and may be intermixed with
		the user commands:

		-l<log file name>  With this option you can give the name of the
                                   log file as output of NHermes. The log file
				   will always have the extention .LOG. If you
				   don't give the extention in uppercase or not
				   at all, it will be added to <log file name>.
				   The default log file name is GIPSY.LOG.

                -s<coa file name>  This option specifies the name of the file
                                   to which the equivalent of COA (screen)
                                   output in tHermes is written.

		-t<minutes>        This is the timer option. After <minutes>
				   minute(s) a time out will occure.
				   The time out provides termination of the user
                                   command when it is stucked in an endless loop

Description:    The non interactive Hermes, NHermes, is based on the interactive
                Hermes, Thermes.
		The user command will be considered as if it was a user type-in
		in the User Command Area of the THermes. Default values for
		unspecified keywords will be taken if available. If there is no
		default available, an error message will be generated in the log
                file and NHermes will stop with the current user command.
		When a user command is done, NHermes will start the next user
		command if given by the user.
		If the time out is set, a time out will occure after <minutes>
		minute(s) and NHermes will stop with the user command it is
		dealing with.
		The time out works seperately for each user command.
		All output will be kept in a log file.

		Consult the manuals of Hermes and the GIPSY servant tasks for
		detailed information about how to build a cola script and how to
                use servant tasks.


		LOGFILE


		NHermes maintains a log file like THermes. This log file
		contains a history of all operations done and also information
		generated by servant processes. The information in the log file
		which is generated by servant processes depends on the settings
		of those processes.


		ENVIRONMENT


		NHermes  should run  in a GIPSY  environment.  Consult the
		GIPSY user manual for the installation of this environment.

See also:       hermes.dc1, thermes.dc1, xhermes.dc1, GIPSY.doc.

Warnings:       SETTINGS OF SERVANT TASKS
		At this moment you can not change any settings (e.g. output
		level) of servant tasks (See XHermes and THermes for settings).

		USER COMMAND IN ARGUMENT LIST
		If the given servant task or cola script with their parametes
		are not suitable quoted or escaped to keep it together, NHermes
		shall handle them as if they were seperate user commands.

Updates:        Fri Jan 31, 1992: MAMR, document created
                   ...
                Tue Nov 16, 1999: JPT, show current parameters for "macro"
                Tue Nov 23, 1999: JPT, keyword file write bug fixed
                Tue Apr  2, 2002: JPT, incorrect task count fixed
                Mon Jun  6, 2011: JPT, added -s option.
                Mon Oct 15, 2011: JPT, fixed bug in error reporting.
#<

#>            thelp.dc1

                           CONTROL KEY DEFINITIONS
================================================================================
                                    HELP
--------------------------------------------------------------------------------
TAB : provide help display. If a task name is present in the user command area,
      the dc1 document of that task is displayed. If there is also a keyword
      present, then the display is positioned at the first occurrence of the
      keyword in the document and detailed information about the keyword
      is given.
      When TAB is pressed for a second time, the help display will disappear.

^V  : page forward in the help display.

^Z  : page backwards in the help display.

^R  :
^S  : search for a text string in the document.

ESC TAB : switch keyword help on or off.

ESC X : start a WWW browser for the display of documentation and help texts.

================================================================================
                              SESSION CONTROL
--------------------------------------------------------------------------------
^L   : rewrite the terminal screen.

^O   : circulate between terminal windows waiting for keyboard input.

^Q   : prompts for user's approval to quit.
^X^C   'Y' terminates the GIPSY session. If any tasks are still active,
Esc Z  tHermes waits until they are finished.
       Any other character causes the session to be continued.

Esc U: pop up or down Unix shell window.

^X U : overlay session with Unix shell.

================================================================================
                               TASK CONTROL
--------------------------------------------------------------------------------
^C   : abort a task; the task name must be present in user command area (UCA).
       It can also be used to break out of a subwindow prompt.

^W   : suspend or resume a task (name must be present in the UCA).

Esc S: change context parameters, e.g. output mode. If a task name is present
       in the UCA, then this task is affected, otherwise the default settings
       for new tasks are changed.

Esc K: display the current user input parameters of the task of which the name
       is present in the UCA. The task need not be active. Pressing Esc K again
       removes this display.

Esc TAB: activates or deactivates explanation screen associated with prompts
         for user input.

Esc E: start editor to edit user input  parameters.

Esc R: if input parameter display active (Esc K): save a single parameter
       in a (recall-) file.

Esc W: if input parameter display active (Esc K): save the displayed set of
       parameters in a (default-) file.

Esc D: change working directory for tasks.

Esc T: change 'path' for tasks. A path consists of one or more directory
       specifications, separated by blanks.

================================================================================
                             COMMON OUTPUT AREA
--------------------------------------------------------------------------------
^V  : page forward in the common output area (COA).

^Z  : page backwards in the COA.

^R  : search backwards for text in the COA.

^S  : search forward for text in the COA.

^P  : switch page mode on or off.

Esc G: go to absolute or relative (+/-) page number.

Esc H: make hardcopy of screen pages to printer (default: current page)

Esc P: select printer for Esc H

Esc <: go to first page of COA

Esc >: go to last page of COA

^X S:  switch scroll on or off

================================================================================
                           COMMAND LINE EDITING
--------------------------------------------------------------------------------
^A  : move to start of user command area (UCA).

^B  : move one position backwards.

^D  : forward delete one character.

^E  : move to end of UCA.

^F  : move one position forward.

^K  : forward delete rest of command.

^U  : clear UCA.

DEL,
^H  : backward delete one character.

^T  : replace UCA contents with the next active task name.

Space : if the UCA is 'free', replace it with the next prompt for user input.

Esc M : switch taskname and keyword minimal matching on or off.

up arrow: step through previous commands in backward direction.

down arrow: step through previous commands in forward direction.

================================================================================
                              Miscellaneous
--------------------------------------------------------------------------------

*   News about changes and improvements in GIPSY can be displayed by running
    the task 'news'.

*   Esc C will switch on or off a calculator window.

*   CTRL-X E  will prompt for a filename to be edited and start an
    editor which will overlay the tHermes screen.

*   tHermes recognizes some ANSI escape sequences:

    Key             Equivalent
    ---             ----------
    left arrow      CTRL-B
    right arrow     CTRL-F
    page up         CTRL-Z
    page down       CTRL-V
    up arrow           -
    down arrow         -

*   There is a limited Unix shell available (/bin/csh). It can be popped up
    and down using "Esc U" and it is terminated with "exit".
    Fancy features like command recall and editing are not available.
    Logging can be switched on and off using "Esc L".

*   The beep which is used in connection with prompts can be switched
    off and on by typing Esc B.

========================= END OF HELP INFORMATION ==============================





















Document:     thelp

Purpose:      file containing default help display for tHermes.

Category:     USER-INTERFACE

File:         hermes.src

Author:       J.P. Terlouw


#<

#>            thermes.dc1
Document:     thermes

Purpose:      GIPSY master control program for ASCII terminals

Category:     USER-INTERFACE

File:         hermes.src

Author:       J.P. Terlouw

Description:  There is a TeX document which describes thermes.
              A copy can be obtained by LaTeX'ing $gip_doc/hermes
              (twice to get the internal references right).

See also:     nhermes.dc1

Updates:      Sep 13, 1991: JPT, document created.
              Oct 11, 1991: JPT, new command parser included.
              Oct 30, 1991: JPT, new features: task path and default files.
              Nov  4, 1991: JPT, 'exact'-option added to user input.
              Nov 11, 1991: JPT, Unix shell; circulate terminal windows.
              Nov 13, 1991: JPT, treat cola scripts as executables.
              Nov 15, 1991: JPT, default file bug fixed; keyword buffer
                                 size increased.
              Dec  2, 1991: JPT, new features: complete revision of user
                                 input handling (recall files, repeated
                                 keywords); Unix shell logging.
              Jul 22, 1992: JPT, added one more keyboard handler level;
                                ^O now also works when help display or
                                 keyword display is on.
              Aug  3, 1992: JPT, improved keyword display and task context
                                 settings menu; implemented default file
                                 writing; change directory logic improved;
                                 some minor improvements.
              Aug 18, 1992: JPT, repaired reporting of non-existent task.
              Aug 27, 1992: JPT, changed to use general printer module.
              Sep  2, 1992: JPT, fixed bug in parse.l (CmdParam);
                                 and some other improvements.
              Sep  7, 1992: JPT, fixed bug in counting active tasks;
                                 added hermes default file option;
                                 improved working directory setting.
              Sep 11, 1992: JPT, eliminated use of putenv(); fixed bug
                                 in rejecting and cancelling keywords;
                                 various small improvements.
              Sep 17, 1992: JPT, implemented overlay Unix shell and
                                 prompt following in help screen.
              Sep 28, 1992: JPT, implemented Tektronix overlay window and
                                 recall file write option.
              Oct  6, 1992: JPT, implemented minimal matching, user
                                 input parameter editing and some
                                 internal improvements.
              Oct 14, 1992: JPT, does not wait anymore for overlay Unix
                                 shell and parameter editor;
                                 eliminated bug in minimal matching;
                                 improved Tektronix window.
              Oct 27, 1992: JPT, improved parameter editor;
                                 implemented command recall.
              Nov  4, 1992: JPT, eliminated bug in minimal match;
                                 improved parameter editor interfacing;
                                 command recall information saved in file;
                                 implemented COA scroll mode.
              Nov 10, 1992: JPT, implemented file edit facility, also
                                 for tasks.
              Nov 25, 1992: JPT, implemented improved prompter and
                                 improved log file text search.
              Dec 18, 1992: JPT, task 'news' is started automatically after
                                 starting tHermes.
              Mar  5, 1993: JPT, keyboard mapping changed.
              Jun 10, 1993: JPT, user input explanation window;
                                 show parameters of task started as !task.
              Jul 20, 1993: JPT, searches also in $HOME for hermes.def
              Jul 26, 1993: KGB, modified all 'Proc identifiers' to long.
              Sep 13, 1993: JPT, improved task minimal matching; now also
                                 <RETURN> completes task name.
              Sep 20, 1993: JPT, implemented BEEP= keyword for hermes.def.
              Sep 22, 1993: JPT, corrected bug in change of Sep 13.
              Nov 23, 1993: JPT, added report hint.
              Nov 25, 1993: JPT, improved task EDITFILE interaction.
              Jan 31, 1994: JPT, removed bug from "Current parameters" output.
              Mar  4, 1994: JPT, added time stamp to screen output;
                                 removed bug from "REPORT hint".
              Jul  4, 1995: JPT, added WWW browser support; context sensitive
                                 help for prompters etc., improved keyhelp.
              Aug 25, 1995: JPT, more robust check for local html presence
              Aug 31, 1995: JPT, help: defined html context for empty UCA
              Mar 19, 1996: JPT, allow command line arguments for WWW browser
              Apr  9, 1996: JPT, some minor corrections
              Apr 10, 2006: JPT, interfaced Firefox to web help.
              Jul 15, 2009: JPT, fix screen page number>9999 crashes (coamgr.c)
#<

#>            hermes.tex
\documentstyle[12pt]{report}
\def\H{{Hermes}}
\def\tH{{tHermes}}
\def\xH{{xHermes}}
\def\nH{{nHermes}}
\def\G{{\sc gipsy}}
\def\COA{{\sc coa}}
\def\UCA{{\sc uca}}
\def\TSA{{\sc tsa}}
\parskip=3mm
\parindent=0mm
\makeindex
\evensidemargin=0cm
\oddsidemargin=0cm
\textwidth=16cm
\textheight=21cm
\begin{document}
%\pagestyle{heading}
\thispagestyle{empty}
\begin{titlepage}
\null
\begin{center}
{\bf\huge Hermes User's Guide\\}
\vskip 2.0 truecm
{\large by J.~P.~Terlouw\\}
\vskip 1.0 truecm
Version of\\
April $10^{\rm th}$, 2006
\end{center}
%\vskip 10.0truecm
\vfill
\begin{quote}
\it
\hspace{3mm}``Hermes was de god, die de wegen en het verkeer daarlangs be\-scherm\-de.
Hij was dan ook de god van de reizigers en de handel, zelfs van de dieven, die
immers ook hun geluk langs de wegen zoeken.
Hij was de bode der goden, toegerust met vleugelschoenen en
heraut\-staf, die op
bevel van Zeus boodschappen overbracht en mensen veilig naar hun doel voerde;
ook geleidde hij de doden naar de onder\-wereld.''
\begin{flushright}
\rm
Uit: Ernst Hoffmann:\\
``Goden- en Heldensagen''\\
bewerkt door Dr.~J.~H.~Croon\\
\end{flushright}
\end{quote}
\vfill
This guide describes the user interface of the
Groningen Image Processing SYstem (\hbox{\G}).
It is meant as both an introduction for new users and
a reference document for more experienced users.

\end{titlepage}
\thispagestyle{empty}\strut\newpage
\thispagestyle{empty}
\pagenumbering{roman}
\tableofcontents
\newpage
\pagenumbering{arabic}
\chapter{Introduction}
\section*{What is \H}
\H\ is the user interface of \G.
It enables users to do multi-tasking in an organised fashion.
Users have direct control over the application programs ({\em tasks\/}) they run:
a running task can be suspended or aborted with a simple command; various
settings can be changed while a task is running.

Parameters for a task can be specified at any time; if a task needs information
that has not been specified yet, the user is prompted.

Context-sensitive help about tasks is provided at a single keystroke.

Tasks keep the user informed in two ways: they write in a log file
in which the user can page and search and they can provide a one-line status
message, which can be frequently updated.

Two versions of \H\ exist:
\begin{itemize}
\item \tH, which runs on standard character display terminals.
This version is used for interactive work.
\item \nH, a non-interactive version intended for batch work.
\end{itemize}
Normally \H\ is not started directly by the user, but rather by a shell script
which prepares a number of settings before starting \H. Refer to the
\G\ users guide.
\section*{Conventions in this document}

Text that appears literally on the terminal or workstation  screen such as
names of programs and commands is presented in {\tt typewriter} font.
Text which designates variable information is presented in $italic$ font.

\begin{quote}
\small Text, presented like this (smaller type and indented margins), contains
detailed information and may be skipped at first reading.
\end{quote}
\chapter{Tasks}

\section{Introduction}
Tasks in \G\ are the application programs which do useful work for the user.
They consist of one or more processes (usually one) which communicate with
the user through a formalized collection of interface routines.
Because of the use of these interface routines, tasks can be run from any
version of \H.

\begin{quote}
\small Though tasks can be run from all versions of \H, not all tasks
can be run {\em successfully} from all versions because a version of \H\ might
not implement all interface functions.
For instance the {\tt EDITFILE} function is not
implemented by \nH.

{\tt COLA}-scripts can be started as tasks. For information
about the use of {\tt COLA} refer to the document {\tt cola.dc1}.
\end{quote}

\section{User input}
\subsection*{Introduction}
Parameters for a task are passed to it via keywords.
A keyword is a character string followed by an equal sign, e.g.\ {\tt INSET=}.
Keyword names are case-insensitive, i.e.\ uppercase and lowercase letters
have the same meaning.
Parameters for a task can be specified in the command that starts the task and
thereafter at any moment while the task is active.
A task requiring input from the user causes \H\ to find out whether it has
already been specified and if not, prompt the user.
Before a parameter value is passed to a task, \H\ first checks whether it
meets the task's request and if not, \H\ rejects that value and prompts again.

As the execution of the task
proceeds, \H\ builds up a table of the task's keywords and associated
values.
At task termination this table is saved in order to allow the user to
run the task again with (partly) the same parameters.

Three classes of keywords are recognized:
\begin{enumerate}
\item
{\em Forced\/}, which are keywords for which no task default is allowed.
The user must respond to the prompt with a correct value.
\item
{\em Defaulted\/},
which are keywords which have a default value defined by the
task. If the keyword has not already been specified then the user
will be prompted.
If on this occasion the user types only a {\tt RETURN}, this
signifies that the task default is acceptable and should be used. Any
other input will override the default.
\item
{\em Hidden\/},
which are keywords for which the user is not prompted and
which have a default value defined by the task.
They can be specified by unprompted input by the user.
The documentation of a task must be read to find out if it uses
hidden keywords.
\begin{quote}
\small The user can set a parameter in the task context to change requests
for hidden keywords into requests for defaulted keywords. (``Unhide'' hidden
keywords.)

Hidden keywords are sometimes used to control looping in a task.
Each cycle round the loop, the task requests the value of a hidden keyword.
If it has not been specified, the task takes the default and looping continues.
If the user defines the keyword then that value is sent to the task which
could cause the task to terminate the loop.
\end{quote}
\end{enumerate}

\subsection*{Parameter syntax}
Basically \H\ is capable of providing tasks with the following types of
parameters:
integer numbers, real numbers, double precision reals, logicals and
character strings.
Tasks may however request input in the form of character strings and perform
their own decoding to obtain numerical information.
One parameter value can contain one or more elements.

\subsubsection*{Character input}
Depending on what the task specifies, character input can be given in two ways:
as {\em text\/} or as an {\em array\/}. When text is requested, all characters
belonging to the keyword, including blanks and comma's, are treated as a single
element.
In the case of an array, the elements must be separated by blanks or comma's.
If the input should contain special characters like (leading) blanks or
equals signs, it can be quoted using back-quotes ({\tt `}).

Normally character input is case-sensitive,
i.e.\ uppercase and lowercase letters have different meanings,
but individual tasks can deviate
from this or convert to either uppercase or lowercase.

\subsubsection*{Number input}
Floating point or integer numbers can be typed as numbers and/or
expressions (see also section ``Number input -- operators,
constants and functions'').
They must be separated by blanks or comma's.

Lists of numbers can be specified using the
`$start${\tt :}$end${\tt :}$increment$'
notation, where the `{\tt :}$increment$' part is optional and defaults to one.
A list of {\it n} identical values can be specified as
`{\it value\/}{\tt ::}{\it n}.'
A list can also be used as an operand in an expression.
In this case the list must be enclosed by square brackets {\tt [ ]} or
parentheses {\tt ( )}.
The expression is then evaluated for each element of the list.

The operator {\tt ?} can be used to select one or more items from a list.
See example below.

Examples:

{\tt
\begin{tabular}{lll}
1 2 3/3  sin(pi)&{\rm yields}&       1.0 2.0 1.0 0.0\\
log(10)::4&{\rm yields}&       1.0 1.0 1.0 1.0\\
log(10):log(100):2/4&  {\rm yields}&       1.0 1.5 2.0\\
10**[0 1 5]     &      {\rm yields}&      1 10 100000\\
{}[1:3]+[90:70:-10]  &     {\rm yields}&       91 82 73\\
{}[20:30]?[3 4 5] & {\rm yields}& 22 23 24\\
\end{tabular}
}

Suppose an inclination at keyword {\tt INCL=} must be given in
degrees, but you want to give the input in axis ratio.

{\tt INCL= deg(acos(0.3 0.5))}

The argument for {\tt acos} is a list consisting of the elements 0.3 and 0.5.
The expression evaluates these two values for {\tt acos} and converts
the resulting values to degrees. In this way the values 0.3 and 0.5
are converted to the angles $72.5424^\circ$ and $60.0^\circ$.

\subsubsection*{Logical input}
Logicals are decoded in the following way: {\tt YES}, {\tt JA} and {\tt TRUE}
result
in a logical which is true; {\tt NO}, {\tt NEE} and {\tt FALSE}
give a logical which
is false. It is sufficient to give the first letter of the possible
affirmative and negative replies. Any other answer will result in
a syntax error.

Examples:
\begin{flushleft}
{\tt
PLOTGRIDS= N\\
OK= YES\\
}
\end{flushleft}
%For logicals the inputs {\tt YES}, {\tt JA} or {\tt TRUE}
%are affirmative (the first letter is sufficient). Other inputs result in
%{\tt NO}.

\begin{quote}
\small
{\bf Input Lists and Recall Files}

If a task requests a keyword repeatedly, the user can pre-specify inputs in
two ways. The first method is separating the inputs with semicolons,
e.g.

{\tt POS= 10 20; 15 25; 30 30}

Note that a trailing semicolon
designates an empty input for which a default is to be taken.
The second method consists of specifying a file
(``{\em recall file\/}'') as input to the keyword.
This must be a text file  with a name like ``{\it name\/}{\tt .rcl}''.
Every line in this file is a separate input. Lines must not be longer than 500
characters.
Either the whole file or a part of it can be specified:

\begin{tabular}{ll}
{\tt <}$name$&use the whole file;\\
{\tt <}$name$ $n${\tt:}$m$&use line numbers $n$ to $m$;\\
{\tt <}$name$ {\tt:}$n$&use line numbers 1 to $n$;\\
{\tt <}$name$ $n${\tt:}&use line numbers $n$ to the end of the file;\\
{\tt <}$name$ $n$ &use only line number $n$.\\
\end{tabular}

Here the $n${\tt:}$m$ range specifier should not be confused with a list of
numbers as described above.

It is possible to specify a recall file in a semicolon-separated list, but
within a recall file semicolons cannot be used. Recall files cannot be used
recursively, i.e.\ a recall file can not contain a reference to an other
recall file.

When the task has ``consumed'' all pre-specified input on a keyword, the next
request for that keyword will cause the user to be prompted in the normal way.
Inputs given on repeatedly requested keywords are stored in a
semicolon-separated list, so if a task is re-run with the previous set of
parameters, {\em all\/} inputs are available again.

If unprompted input is given on a keyword of which all pre-specified input has
not been consumed yet, the new input will supersede the {\em complete\/}
previous set of inputs.

\newpage
{\bf Default files}

Keywords can be prespecified in a text file with a name like
``{\it name\/}{\tt .def}''. This {\em default file\/} can contain any
number of {\it keyword\/}{\tt =}{\it value}\/ pairs.
Any user supplied parameters will supersede those obtained from a default file.

The name of the {\em task} determines which default file will be read, not the
name of the executable. This has consequences (and possibilities) for tasks
which are run under an alias name.

It is also possible to specify defaults to be used by every task. For this
purpose the file {\tt tasks.def} us used. Such global defaults have
a limited use. The keyword {\tt GGIOPT=}, used by tasks with a graphical user
interface, is an example.

Default files can reside in both the current working directory and the
directory {\tt .gipsy} under the user's
login directory. Keywords from default files in the working directory supersede
identical keywords from {\tt .gipsy}. And keywords from task-specific
default files supersede identical global keywords.

\end{quote}

\clearpage
\subsubsection*{Number input -- operators, constants and functions}
In numerical expressions the following operators are available:

{\small
\begin{tabular}{ll}
{\tt +}&addition \\
{\tt -}&subtraction \\
{\tt *}&multiplication \\
{\tt /}&division \\
{\tt **}&exponentiation \\
\end{tabular}
}

The following predefined constants are available:

{\small
\begin{tabular}{ll}
{\tt PI}&$\pi$ (3.14159....) \\
{\tt C}&speed of light (SI) \\
{\tt H}&Planck (SI) \\
{\tt K}&Boltzmann (SI) \\
{\tt G}&gravitation (SI) \\
{\tt S}&Stefan-Boltzmann (SI) \\
{\tt M}&mass of Sun (SI) \\
{\tt P}&parsec (SI) \\
{\tt BLANK}&universal undefined value \\
\end{tabular}
}

The following mathematical functions can be used:

{\small
\begin{tabular}{ll}
{\tt abs({\it x})}&absolute value of {\it x}\\
{\tt sqrt({\it x})}&square root of {\it x}  \\
{\tt sin({\it x})}&sine of {\it x}\\
{\tt asin({\it x})}&inverse sine of {\it x}\\
{\tt cos({\it x})}&cosine of {\it x}\\
{\tt acos({\it x})}&inverse cosine of {\it x}\\
{\tt tan({\it x})}&tangent of {\it x}\\
{\tt atan({\it x})}&inverse tan of {\it x}\\
{\tt atan2({\it x},{\it y})}&inverse tan (mod 2$\pi$)\\
 &{\it x} = sin, {\it y} = cos\\
{\tt exp({\it x})}&exponential of {\it x} ($e^x$)\\
{\tt ln({\it x})}&natural log of {\it x}\\
{\tt log({\it x})}&log (base 10) of {\it x}\\
{\tt sinh({\it x})}&hyperbolic sine of {\it x}\\
{\tt cosh({\it x})}&hyperbolic cosine of {\it x}\\
{\tt tanh({\it x})}&hyperbolic tangent of {\it x}\\
{\tt rad({\it x})}&convert {\it x} to radians\\
{\tt deg({\it x})}&convert {\it x} to degrees\\
{\tt erf({\it x})}&error function of {\it x}\\
{\tt erfc({\it x})}&1-error function\\
\end{tabular}
\begin{tabular}{ll}
{\tt max({\it x},{\it y})}&maximum of {\it x} and {\it y}\\
{\tt min({\it x},{\it y})}&minimum of {\it x} and {\it y}\\
{\tt sinc({\it x})}&sin({\it x})/{\it x} (sinc function)\\
{\tt sign({\it x})}&sign of {\it x} ($-1$, 0, 1)\\
{\tt mod({\it x},{\it y})}&remainder of {\it x}/{\it y}\\
{\tt int({\it x})}&truncates to integer\\
{\tt nint({\it x})}&nearest integer\\
{\tt ranu({\it x},{\it y})}&generates uniform noise between\\
&{\it x} and {\it y}\\
{\tt rang({\it x},{\it y})}&generates gaussian noise with\\
&mean {\it x} and dispersion {\it y}\\
{\tt ranp({\it x})}&generates poisson noise with mean {\it x}\\
\\
{\tt ifeq({\it x},{\it y},{\it a},{\it b})}&
{\it a} if $x=y$, else {\it b} \\
{\tt ifne({\it x},{\it y},{\it a},{\it b})}&
{\it a} if $x \neq y$, else {\it b} \\
{\tt ifgt({\it x},{\it y},{\it a},{\it b})}&
{\it a} if $x > y$, else {\it b} \\
{\tt ifge({\it x},{\it y},{\it a},{\it b})}&
{\it a} if $x \geq y$, else {\it b} \\
{\tt iflt({\it x},{\it y},{\it a},{\it b})}&
{\it a} if $x<y$, else {\it b} \\
{\tt ifle({\it x},{\it y},{\it a},{\it b})}&
{\it a} if $x\leq y$, else {\it b} \\
\end{tabular}
}

The following functions can be used to obtain data from \G -sets and files:

{\small
\begin{tabular}{ll}
{\tt descr({\it set}, {\it name})}&descriptor item {\it name}\/ from
(sub)set(s) {\it set}.\\
{\tt table({\it set}, {\it tab}, {\it col}, {\it rows})}&
cell(s) from column {\it col}\/ of table {\it tab} in (sub)set {\it set}.\\
{\tt image({\it set}, {\it box})}&  pixel(s) from (sub)set {\it set}.\\
{\tt file({\it name}, {\it cols}, {\it rows})}&number(s) from a column in 
a text file.\\
\end{tabular}
}

The {\it set}- and {\it box}\/ arguments have the same syntax as described
in {\tt input.doc}.
The argument {\it rows} has the same syntax as is used for recall files
($n${\tt:}$m$, {\tt:}$n$, $n${\tt:} and  $n$).
Lines in the file to be read by the {\tt file}-function must not be longer than
500 characters.
Comments can be included in the file by prefixing it by one the characters
{\tt !} or {\tt \#}.
All items in the file should be numbers.
If an item cannot be recognized, the value {\tt BLANK} will result.
In this way {\tt BLANK}s can also be specified explicitly, e.g. by using
the word ``BLANK''. Empty columns at the end of a line will also yield
{\tt BLANK}s. But a completely empty or blank line is treated as a comment.

The following functions can be used on lists:

{\small
\begin{tabular}{ll}
{\tt count($x$)}&number of elements in $x$\\
{\tt mean($x$)}&the average of the elements in $x$\\
{\tt sum($x$)}&the sum of the elements in $x$\\
\end{tabular}
}

\H\ also supports named variables.
To these variables numbers and lists of numbers can be assigned. 
Variable names can be up to 20 characters long; they are not case-sensitive.
The maximum number of variables is 40.
A variable can be deleted by assigning the value {\tt BLANK} to it.
Assignment to a variable is accomplished by issuing a special \H\ command:

{\it variable}{\tt =}{ \it value}

or, in \tH, doing the assignment in the \tH\ calculator window (see page
\pageref{calculator}).

\section{Task context}
Tasks all run in a {\em context\/}. This context consists of a number of
user-settable parameters:
\begin{itemize}
\item
Error level. If a task generates an error at or above the current error level,
\H\ will abort the task. Default: 4.
\item
Message level. If a task generates an error at or above the current message
level,
\H\ will display the associated error message. Default: 1.
\item
Output mode. This parameter determines whether test messages from tasks will be
displayed and whether messages unnecessary for experienced users will be
suppressed. Default: ``{\tt NORMAL}''.
\item
Device status. Two parameters indicating whether messages sent to either the
screen or the log file will actually be written.
Default: screen ``{\tt ON}'' and log file ``{\tt ON}''.
\item
Hide status. This parameter determines whether ``hidden'' user input requests
from the task will cause the user to be prompted anyway. Default: ``{\tt ON}'',
i.e.\ the user will not be prompted for hidden keywords.
\item
Working directory. Default: the directory from which \H\ was started.
The directory of running tasks cannot be changed.
\item
Finally there are four parameters which determine what kind of status messages
will be logged.
\end{itemize}
Tasks normally inherit their context from a template context. If a task is
started by an other task, it inherits the context of that task.
Both the template context and the context of any running task can be changed by
the user. The way in which this can be done depends on the specific
implementation of \H.
When \H\ is started, the template context contains the sensible defaults
listed above, possibly modified by parameters from the \H\ defaults file
(see below).

\section{\H\ defaults file}
The template task context and some other parameters can be defined in a
defaults file which will be read when \H\ is started.
This file must have the name {\tt hermes.def} and reside in the directory
from which \H\ is started or in the directory {\tt .gipsy} under the user's
login directory. (Parameters
from the file in the start-up directory have precedence.)
Its format is a text file which may contain any number of the keyword-value
pairs from the table below.

\begin{tabular}{ll}
{\tt DIRECTORY=}&working directory for tasks.\\
{\tt TASKPATH=}&search path for tasks.\\
{\tt NTASK=}&number of task entries; must be $>0$.\\
{\tt HIDE=}&hide status for keywords; must be {\tt ON} or {\tt OFF}.\\
{\tt ERRORLEVEL=}&error level; must be in the range 1 to 4.\\
{\tt MESSAGELEVEL=}&message level; must be in the range 1 to 4.\\
{\tt OUTPUTMODE=}&
output mode; must be {\tt NORMAL}, {\tt EXPERT} or {\tt TEST}.\\
{\tt SCREEN=}&screen output status; must be {\tt ON} or {\tt OFF}.\\
{\tt LOGFILE=}&log file output status; must be {\tt ON} or {\tt OFF}.\\
{\tt PRINTER=}&printer number for screen hardcopies.\\
{\tt MATCH=}&minimal matching; must be {\tt ON} or {\tt OFF}.\\
{\tt TEKDEVICE=}&Tektronix terminal type (\tH\ only).\\
{\tt BEEP=}&terminal bell; must be {\tt ON} or {\tt OFF} (\tH\ only).\\
{\tt KEYHELP=}&
add keyword help to standard help; must be {\tt ON} or {\tt OFF}.\\
{\tt WEB\_BROWSER=}&full-path location of Hypertext browser and optional
command line arguments.\\
{\tt STARTBROWSER=}&start Hypertext browser automatically.\\
\end{tabular}

\vspace{1cm}
Example of a typical {\tt hermes.def} file:

\begin{quote}
\begin{verbatim}
taskpath=. $HOME/owntasks/$gip_arch
directory=/dj3/users/cia/gipsy
outputmode=expert
match=on keyhelp=off
printer=3
\end{verbatim}
\end{quote}

\chapter{\tH}
\section{Introduction}
\tH\ is the version of \H\ which communicates with the user through standard
character display terminals, including X11 terminal windows.
It organises the terminal screen in a special window-like way.
The most important permanent components (windows) of the screen lay-out are:
\begin{itemize}
\item
User Command Area (\UCA ). This occupies the lower two lines of the terminal
screen and is the focus of user-to-task communication.
\item
Task Status Area (\TSA ). This normally consists of four lines above the {\UCA}
which each can contain information reflecting the current status of a task.
\item
Common Output Area (\COA ), the remaining top part of the screen which
shows a part of the log file.
Here user commands are logged and tasks can write text.
\end{itemize}
Other permanent components are a clock display and numbers which indicate
which part of the log file is mapped to the \COA .

Components of the screen lay-out which are not permanent are called transient
windows.
These overlay the permanent screen components or other transient windows.
For example the context-sensitive help display overlays (part of) the \COA .
Other examples of transient windows are
a menu window allowing the user to change specific task settings, prompter
windows in which for instance a search string can be specified, a Unix shell
window, etc.

Many functions of \tH\ can be invoked by typing a control key (designated
as {\tt CTRL-}{\it c}, where {\it c} is a letter), or by typing an escape
sequence (the {\tt ESC} character followed by another character).
These are summarized in the tables on pages \pageref{escseqs} and
\pageref{ctrlkeys}.
Important control keys are: {\tt CTRL-C} to abort
a task or to break out of a transient window, {\tt CTRL-O}\label{ctrlo}
to switch the
``keyboard focus'' between terminal windows waiting for keyboard input,
{\tt CTRL-L}\label{ctrll} to repair a damaged screen lay-out
and {\tt CTRL-Q}\label{ctrlq} to quit from \H.
\H\ can also be terminated by typing {\tt CTRL-X CTRL-C}\label{ctrlxctrlc} or
{\tt ESC Z}\label{escz} or by typing {\tt QUIT}, {\tt STOP}, {\tt END} or
{\tt EXIT} in the \UCA.

It is not possible to run more than one \G\ session in the same directory.
If \tH\ is started in a directory where \G\ already runs, it exits
immediately with an error message.
(But \nH\ will wait until the other session is finished.)

\section{User Command Area}
The User Command Area (\UCA ) consists of the bottom two lines of the terminal
screen where most interaction with the tasks takes place.
The user can type task-related commands in the \UCA\ and \H\ can put there
task-related prompts.
An important aspect of the prompts is that they
are treated in exactly the same way as information typed by the user, i.e.\ the
user can erase or modify a prompt supplied by \H\ as if he typed it himself.
Prompts in the \UCA\ are always in the form of a legal complete or partial
command.

The information in the \UCA\ is used as the context for a number of functions of
\H.
E.g.\ {\tt TAB} (or {\tt CTRL-I}) causes the user document of any taskname
present in the
\UCA\ to be displayed and {\tt CTRL-C} aborts the task if it is active.

The two lines of the \UCA\ are one logical long line which can be edited using
the following control keys of which most are {\sc emacs}-compatible.

\label{ctrla}\label{ctrlb}\label{ctrld}\label{ctrle}\label{ctrlf}
\label{ctrlk}\label{ctrlt}\label{ctrlu}
\begin{tabular}{ll}
{\tt CTRL-A}&move to start of line\\
{\tt CTRL-B}&move one position back\\
{\tt CTRL-D}&forward delete one character\\
{\tt CTRL-E}&move to end of line\\
{\tt CTRL-F}&move one position forward\\
{\tt CTRL-H}&backward delete one character\\
{\tt CTRL-K}&forward delete rest of line\\
{\tt CTRL-T}&replace complete line with next active task name (see below)\\
{\tt CTRL-U}&delete complete line\\
{\tt DEL}&backward delete one character\\
{\tt space}&replace complete line with next prompt for user input (see below)\\
\end{tabular}

\begin{quote}
\small
\parskip=0mm
Characters {\tt CTRL-T} and {\tt space} are somewhat special.
{\tt CTRL-T} erases the \UCA\ and then prompts with the name of an active task.
It does this in a circular fashion so if more than one task is active,
another taskname is displayed when {\tt CTRL-T} is pressed again.

If the \UCA\ is ``free'' (a subtle concept explained below), pressing the space
bar does a similar thing for tasks waiting for input. This makes it easier for
the user to do something else before he supplies the requested input, e.g.
run another task which supplies information that helps to give the input.

The \UCA\ is ``free'' when it is empty, or when it only contains an unmodified
prompt supplied by \H.
\end{quote}

\H\ can provide `minimal matching' for task names and user input keywords
typed in the \UCA.
This facility can be switched on and off by typing {\tt ESC M}.\label{escm}
It can also be switched on using the statement {\tt MATCH=ON} in the
\H\ defaults file.
If it is on, task names will be completed when a blank
or {\tt RETURN} is typed on a command line that only
contains an abbreviated task name.
Keywords will be completed when `{\tt =}' is
typed. If the keyword is ambiguous, \H\ will beep and refuse to accept the
keyword; if it is not known, \H\ will also beep but will accept the
keyword.\newline
\H\ obtains the taskname and
keyword information from the {\tt .dc1} documents in {\tt \$gip\_tsk}.
For this reason minimal matching does not work for tasks which are not
installed.

Previously entered commands can be brought back to the \UCA\ by using the
\label{uparrow}\label{downarrow} ANSI up-arrow and down-arrow keys.
If the user has typed some text in the
\UCA, only commands which match this text will be brought back.
The commands are saved in the text file {\tt HERMES.cmd}, so that they
are preserved between sessions.

\section{Task Status Area}
The Task Status Area (\TSA ) is the middle section of the terminal screen.
It normally consists of four lines which each can contain status information
of an active task.
\begin{quote}
\small
\parskip=0mm
The number of these status lines can be specified using the keyword
{\tt NTASK=} in {\tt hermes.def}.
\end{quote}
The format of a task status entry is one of the following:
\begin{itemize}
\item When a task has been activated, but is not running yet:\\
{\it taskname \hspace{5mm} \tt WAITING TO BE RUN}
\item When a task is running:\\
{\it taskname \hspace{5mm} \tt RUNNING}\\
or\\
{\it taskname \hspace{5mm}  status message supplied by  task}
\item When a task is prompting for user input:\\
{\it taskname \hspace{5mm}  prompt message supplied by  task}
\item When a task is waiting for a file to be edited by the user:\\
{\it taskname \hspace{5mm}  message supplied by  task}
\item When a task is suspended:\\
{\it taskname \hspace{5mm} \tt PAUSING }\\
or\\
{\it taskname \hspace{5mm} \tt PAUSING \it message supplied by  task}
\item When a task is waiting for an other task to finish:\\
{\it taskname \hspace{5mm} \tt WAITING FOR \it other taskname}
\item When a task has finished processing normally:\\
{\it taskname \hspace{5mm} \tt +++ FINISHED +++}
\item When a task has been aborted by the user:\\
{\it taskname \hspace{5mm} \tt USER ABORT}
\item When a task has encountered an error fatal to execution:\\
{\it taskname \hspace{5mm} error message \tt -FATAL}
\item When a task has crashed (i.e.\ exited without notification to \H):\\
{\it taskname \hspace{5mm} \tt CRASHED}
\item When a task cannot be started (incorrect executable,
e.g.\ compiled for a different architecture):\\
{\it taskname \hspace{5mm} \tt COULD NOT BE STARTED}
\end{itemize}

\section{Common Output Area and Log File}
The Common Output Area (\COA ) on the terminal screen is the remaining top area
of the terminal screen not used for the \UCA\ or \TSA .
It is a viewport on the log file.
\H\ treats the log file as a ``book'' with numbered pages.
Normally \H\ shows the current page of the book, that is the page on
which output is currently being written.

The \COA\ has two modes of operation: page mode and non-page mode.
In non-page mode the page on the screen is always the current page.
If necessary, \H\ will flip the page. The current page number is displayed at
the extreme right of the Task Status Area.
In page mode the information on the screen will stay there until the user
instructs \H\ to change the page.
Though the user does not see it, output can still be written to the log file.
In page mode the number of the displayed
page is shown to the left of the current page number.
Page mode can be switched on and off by pressing {\tt CTRL-P}.\label{ctrlp}

When in non-page mode, \H\ can also scroll the \COA\ instead of flipping
pages. This scroll mode can be switched on and off by typing \label{ctrlxs}
{\tt CTRL-X S}.
In scroll mode output is much slower than in `flip mode'. Therefore this
mode is not recommended for use with slow terminals.

The log file can be traversed in backwards direction by pressing
the {\tt CTRL-Z}\label{ctrlz} key (or on ANSI keyboards the
{\tt PageUp} key).
Paging forward is done with the {\tt CTRL-V}\label{ctrlv}\label{ctrlj} key
(or ANSI {\tt PageDown}).

Text search in the log file is done by pressing {\tt CTRL-R}\label{ctrlr}
(reverse) or
{\tt CTRL-S}\label{ctrls}
(forward). \H\ then prompts the user for a search string.
Searching in \H\ is case-insensitive.

A specific page number can be brought to the screen by typing {\tt ESC G},
\label{escg} whereafter \H\ prompts for a page number.
Signed numbers are treated as relative page numbers.

Sections of the log file can be printed by typing the {\tt ESC H}\label{esch}
sequence.
\H\ then prompts for a range of pages to be printed.
The range can be specified as a single number, in which case only that page
will be printed, or using the format `{\it start\/}{\tt :}{\it end\/}'.
Either {\it start\/} or {\it end\/} may be omitted, in which case the number
of the currently displayed page will be used instead.
The default range is the page currently displayed and the preceding two pages.
The printer can be selected by typing {\tt ESC P}, \label{escp} which causes
a menu to be presented from wich a printer can be chosen.
Instead of a printer, output can also be written to a file. To achieve this,
choose option {\tt F} from the printer selection menu. This redirection
can be cancelled by specifying an empty string.

\begin{quote}
\small
The statement that the \COA\ is a viewport on the log file is not quite true.
In fact the log file and the \COA\ are different output streams to which tasks
can send information selectively.
Most tasks however send the same information to both the log file and the \COA .
\end{quote}

\subsection*{COA command summary}

\begin{tabular}{ll}
{\tt CTRL-P}&enter or leave page mode\\
{\tt CTRL-Z}&display previous page and enter page mode\\
{\tt CTRL-V}&display next page\\
{\tt CTRL-R}&search forward for text string\\
{\tt CTRL-S}&search backwards for text string\\
{\tt CTRL-X S}&toggle scroll mode\\
{\tt ESC G}&go to specified page number\\
{\tt ESC H}&make hardcopy of specified page numbers\\
{\tt ESC P}&select a printer for the hardcopy print command\\
{\tt ESC <}&go to first page\\
{\tt ESC >}&go to last page\\
\end{tabular}

\section{Running Tasks}
\subsection*{Starting tasks and supplying parameters}
Tasks can be started with or without specifying parameters.
To start a task without parameters, the name of the task must be typed in the
\UCA , followed by a {\tt RETURN}:

{\it taskname}

To specify parameters, any number of keyword-value pairs separated
by blanks can be added to the taskname:

{\it taskname
keyword\/${}_1${\tt =}value\/${}_1$
keyword\/${}_2${\tt =}value\/${}_2$ $\ldots$}

This format is also used to supply parameters to an already running task.

If there is something wrong with the command, an error message is displayed
in the lower right corner of the screen.

\begin{quote}
\small
Tasknames may be preceded by an explicit directory. In this case, the task must
be present in the specified directory; the task path is not searched.
The directory may contain one or more environment variables
(indicated by a leading {\tt \$}-character).

Tasks can be run again with (partly) the same parameters by prefixing the
task start command with an exclamation mark ({\tt !}).
Any explicitly specified parameters will supersede the corresponding old
parameters. In this case the default file is not used.
For historical reasons the previous parameters of a task are called the
task's {\em macro\/}.

Tasks can be run under a different name (``alias''). The command to do this is:

{\it aliasname\tt (\it filename\tt ) $\ldots$}

{\it Aliasname} is the taskname under which the task will run and
{\it filename\/} is the name of the task's executable file, specified in the
correct case (lowercase for standard \G\ tasks).
\end{quote}

\subsection*{Inspecting and saving input parameters}
The current set of keyword-value pairs of a task can be inspected by
typing {\tt ESC K}\label{esck} while the name of the task
is present in the \UCA.
This causes (part of) the \COA\ to be overlaid with a display containing the
current user input parameters of the task.
Whenever there is a change in one of the parameters, the display will
be adjusted.
If the amount of space on the screen is not sufficient to show all parameters,
the user can page through the display just like paging through the log file.
{\tt CTRL-V}\label{ctrlvv} pages forward; {\tt CTRL-Z}\label{ctrlzz} pages
backwards.
To inspect its keywords, the task need not be active.
Typing {\tt ESC K} again will remove the overlay screen.

The displayed set of parameters can be saved in a file which can later be used
as a default file. This can be achieved by typing {\tt ESC W},
\label{escw} which causes
the user to be prompted for a filename (default: {\it taskname\/}{\tt .def}).

The contents of a single keyword from the displayed set of parameters
can be saved in a file which can later be
used as a recall file. Type {\tt ESC R}.
\label{escr}The user is first prompted for the
keyword, then for the filename.

The parameters of a task can be edited using a standard editor.
The editor used is the one defined by the {\tt EDITOR} environment variable
and it is invoked by typing {\tt ESC E}.\label{esce}
If there is currently a set of parameters displayed on the screen (as the
result of typing {\tt ESC K}), these parameters will be edited;
otherwise the name of the task is derived from the \UCA\ contents.
`Behind the scenes' \H\ will remain active, but user communication with \H\
is not possible because all typing goes into the editor.
Using this editor is the only way in \tH\ to change input parameters for
a task that is not active.\newline
If \H\ cannot derive a name from either the parameter display or the \UCA,
it will prompt for a file name as if {\tt CTRL-X E} was typed.

\subsection*{Aborting, suspending and resuming}
Tasks can be aborted by typing {\tt CTRL-C}\label{ctrlc}.
They can be suspended and resumed by typing {\tt CTRL-W}\label{ctrlw} or
{\tt CTRL-G}\label{ctrlg}.
If more than one task is active, the target task's name must be present in
the \UCA .

\subsection*{Changing the task context}
The context of a task can be changed using a pop-up menu that can be activated
with the sequence {\tt ESC S}.\label{escs}
The following context parameters can be changed with this menu:
\begin{verse}
- error level: 1--4\\
- message level: 1--4\\
- output mode: normal, expert or test\\
- screen output: on or off\\
- log file output: on or off\\
- hide ``hidden'' keywords: on or off\\
\end{verse}
To change the context of a running task, its name must be present in the \UCA.
If the \UCA\ is empty, the template context will be changed.

\subsection*{Directories and paths}
The working directory can be changed by typing {\tt ESC D}.\label{escd}
\H\ then prompts the user for a new directory.
This directory will then be used for tasks started after the change.
Running tasks and \H\ itself are not affected.

The task path can be changed with {\tt ESC T}.\label{esct}
Upon the prompt that follows one or more directories
separated by blanks can be specified.
A directory may contain (or consist of) one or more environment variables
(indicated by a leading {\tt \$}-character).
Whenever a task start command without an explicit path is given,
this list is scanned to find the task.
If it is not in one of the specified directories, \H\ tries to find the task in
{\tt \$gip\_exe}.
The default path is `{\tt .}' (the current working directory).

\subsection*{Getting help}
To obtain help information about a task or about \H, {\tt TAB} (or {\tt CTRL-I})
\label{ctrli} can be pressed.
This causes (part of) the \COA\ to be overlaid with a display containing the
user document of the task of which the name is present in the \UCA .
If there is also a keyword present (e.g.\ as the result of a prompt),
\H\ positions the document at the first occurrence of the keyword.
In this case \H\ will also provide a keyword help window which partially
overlays the task documentation. For more information see the section
``Keyword help'' below. This feature can be switched off by putting the
statement {\tt KEYHELP=OFF} in the \H\ defaults file ({\tt hermes.def}).

The help display will `follow' the task execution; if other prompts appear,
the display will be repositioned.
If the amount of space on the screen is not sufficient to show the whole
document,
the user can page through the display just like paging through the log file.
{\tt CTRL-V}\label{ctrlvvv}  pages forward; {\tt CTRL-Z}\label{ctrlzzz}
pages backwards.
If the \UCA\ is empty, pressing {\tt TAB} will display
a summary of \H' commands.

It is possible to search for a text string in the document. Pressing
{\tt CTRL-S}\label{ctrlss} or {\tt CTRL-R}\label{ctrlrr} will cause the
user to be prompted for a search string.

Pressing {\tt TAB} again will remove the overlay screen(s).

\subsubsection*{Hypertext help}
Typing {\tt ESC X}\label{escx} will cause Hermes to start a WWW browser.
The \G\ start-up script attempts to determine which browser can be used.
This choice can be overridden by defining the environment variable
{\tt WEB\_BROWSER} before starting \G\ or by defining the variable
{\tt WEB\_BROWSER} in the \H\ defaults file ({\tt hermes.def}).
Any command line options to the browser can also be specified in the
defaults file (but {\em not\/} in the environment variable).
When the defaults file variable {\tt STARTBROWSER} is set to ``{\tt ON}'',
the browser will be started automatically. 

Initially the home page of the new-style \G\ documentation in hypertext
will appear. When the browser is either NCSA Mosaic, Netscape or Firefox,
pressing {\tt TAB} will display the task document in the browser instead 
of overlaying the \COA.

When {\tt ESC X} is typed again, the browser will be terminated and \H\ will
revert to normal operation.

\subsection*{Keyword help}
\H\ can provide specific help about user input keyword prompts.
This can be switched on and off independently of task-related help
by typing {\tt ESC TAB}.
When it is active, the available options are displayed in the bottom line of
the window. The possible options are:

\begin{tabular}{ll}
{\tt PageUp}&page back in window\\
{\tt PageDown}&page forward in window\\
{\tt ESC $i$}&go to reference $i$ ($i=1\ldots 9$)\\
{\tt ESC -}&go back one reference\\
{\tt ESC 0}&go back to ``root text''\\
\end{tabular}

The window can be temporarily removed by removing the associated prompt by
typing {\tt CTRL-U}. When the prompt is brought back by typing a space, the
window will re-appear.

\subsection*{Task control summary}
\begin{tabular}{ll}
{\it taskname}&start a task\\
{\it taskname
keyword\/{\tt =}value\/ $\ldots$}&start a task and/or supply parameters\\
{\tt !}{\it taskname}&start a task with the previous set of input parameters\\
{\tt !}{\it taskname
keyword\/{\tt =}value\/ $\ldots$}&like {\tt !}{\it taskname\/}, but given
parameters replace previous\\
{\it taskname}{\tt (}{\it filename\/}{\tt )} $\ldots$&
run executable {\it filename\/} as task {\it taskname}\\
\end{tabular}

\begin{tabular}{ll}
{\tt CTRL-C}&abort task\\
{\tt CTRL-G}&suspend or resume task (synonym of {\tt  CTRL-W})\\
{\tt CTRL-I}&(={\tt TAB}) activate or deactivate help display\\
{\tt CTRL-W}&suspend or resume task (synonym of {\tt  CTRL-G})\\
\\
{\tt ESC D}&change working directory\\
{\tt ESC K}&activate and deactivate user input parameter display\\
{\tt ESC E}&edit parameters or user-specified file\\
{\tt ESC W}&save displayed parameters in (default) file\\
{\tt ESC R}&save contents of keyword in (recall) file\\
{\tt ESC S}&activate menu to modify task context\\
{\tt ESC T}&change task path\\
{\tt ESC TAB}&switch user input keyword help display on or off\\
\end{tabular}
\section{Unix shell and text editor}
\subsection*{Integrated shell}
Typing {\tt ESC U}\label{escu} causes the \COA\ to be overlaid by a Unix
shell window or,
if the Unix shell is already present, causes the window to be popped down.
The shell used is {\tt /bin/csh}.
Because the shell is not connected to
a real terminal, functionality is somewhat limited: e.g.\ a screen editor
would not work.
Also some commands behave differently, e.g.\ {\tt ls} produces a one-column
list. (A multi-column list can be obtained with {\tt ls -C}.)

By default information displayed in the Unix shell window is not logged.
Logging to the \COA\ and log file can be enabled (and disabled) by typing
{\tt ESC L}.\label{escl}

This shell can be completely terminated by typing {\tt exit} in the shell
window.
\subsection*{Overlay shell}
Typing {\tt CTRL-X U}\label{ctrlxu} causes a Unix shell to be started which
overlays the whole terminal screen.
`Behind the scenes' \H\ will remain active, but because all typing goes into
the shell, user communication with \H\ is not possible.
The shell used is the one defined by the {\tt SHELL} environment variable or,
if this is not defined, {\tt /bin/csh}.

This shell can be terminated by typing {\tt CTRL-D} or {\tt exit}.
\subsection*{Text editor}
Typing {\tt CTRL-X E}\label{ctrlxe} causes the user to be prompted for the name
of a file to be edited by a text editor which will overlay the whole terminal
screen.
`Behind the scenes' \H\ will remain active, but because all typing goes into
the editor, user communication with \H\ is not possible.
The editor is the one defined
by the environment variable {\tt EDITOR};
if this is not defined, the \G\ start-up script will attempt to find a
one of the editors {\tt mem}, {\tt vi} or {\tt emacs} in the user's path.
If no editor can be found, an error message is displayed.

Tasks can also request that a text editor be started. In this case \H\ prompts
the user to allow the editor to be started. To start the editor,
type {\tt RETURN}; to cancel the edit, {\tt CTRL-C} can be typed and the
edit can be postponed by shifting the keyboard focus away from the prompt
window by typing {\tt CTRL-O}.

\section{Tektronix graphics}
If the terminal at which \H\ runs is capable of emulating a Tektronix
graphics terminal, {\tt CTRL-X T}\label{ctrlxt} can be typed to switch on
the graphics mode.
This mode can be used in connection with tasks for which the
plot device {\tt THERTEK} was selected.
If the terminal type has not been defined yet, the user will be prompted.
This information can also be specified in the \H\ defaults file
{\tt hermes.def}. In this case use: {\tt TEKDEVICE=}{\it terminaltype\/}.
Allowable terminal types are listed in the table below.

In Tektronix mode, \H\ continues to operate normally,
but user interaction is somewhat limited. To protect the
graphics image, characters typed are not echoed and the overlay Unix shell
and editors cannot be started.

Typing {\tt CTRL-X T} again will switch Tektronix mode off.
If possible, \H\ then switches back to normal terminal mode, but some
terminals do not support this; these terminals must be switched back
manually by the user.

\H\ `remembers' the contents of the graphics image, so if Tektronix mode is
switched on again, the image is restored.

\vspace{5mm}
\begin{center}
\begin{tabular}{|l|l|l|}
\hline
Type&&switch back\\
\hline
{\tt LADA}&KGB's LAT terminal emulator&automatic\\
{\tt NCSA}&NCSA Telnet&manual\\
{\tt XTERM}&X Window terminal emulator&automatic\\
\hline
\end{tabular}
\end{center}

\section{Calculator}
\label{calculator}
Typing {\tt ESC C} causes a calculator window to be popped up.
In this window, the user can type any numerical expression that would also
be acceptable as user input. The resulting value is displayed in the bottom
line of the window; for lists only the first and last value are shown.
It is also possible to assign a value to a variable.

The calculator window can be removed by typing {\tt ESC C} again.

\chapter{\nH}
\section{Introduction}
\nH\ (non-interactive Hermes) is the version of \H\ for a
non interactive (i.e.\ batch-)  environment.
It is derived from the interactive \tH.

Because the user is not available to be consulted,
default values for unspecified keywords will be taken if available.
If there is no default available, an error message will be written to the log
file and \nH\ will stop with the current command.
When a command is finished, \nH\ will start the next command if given.

If the timeout option is set, a timeout will occur after the user supplied
number of minutes and \nH\ will terminate the command it is
dealing with at that time.
The timeout works separately for each user command.

\nH\ maintains a log file to which a history of all operations is written.
This file can be inspected or printed afterwards.

\section{Using \nH}
The syntax for starting \nH\ is:
\begin{flushleft}
{\tt nhermes -l{\it logfile} -t{\it minutes} {\it command ... command}}
\end{flushleft}

The options may appear in any order and may be intermixed with the
commands. The {\tt -l} and {\tt -t} flags are optional.

The log file will always have the extension {\tt .LOG}
and it contains a history of all operations done and the information
generated by the tasks which have been run.
The default log file name is {\tt GIPSY.LOG}.

The purpose of the timeout option is to terminate the user command when it
uses more time than is thought to be reasonable, e.g.\ if it is executing an
endless loop due to a program or user error.

{\it command} can be a {\tt COLA} script or a name of a  task with their
parameters.
The name of the  task or the {\tt COLA} script with the
parameters must be suitably quoted to keep it together,
e.g.\ between double quotes. If this is not done,
\nH\ will handle them as if they were separate user commands.

Example:

{\tt nhermes -lMylog "FLUX INSET=cg1517 v"}

This command causes \nH\ to start task {\tt FLUX} on all subsets of set
{\tt cg1517} and put the results in file {\tt Mylog.LOG}.

Status return:

If the last command executed by \nH\ was successful, \nH\ exits with status
code 0; when the last command failed, the value 1 is returned. 
\appendix
\chapter{tHermes key definitions}
\subsection*{Escape sequences}
\label{escseqs}
\begin{tabular}{ll}
{\tt ESC B}&switch beep associated with prompts on or off\\
{\tt ESC C}&pop up and down calculator window (\pageref{calculator})\\
{\tt ESC D}&change working directory (\pageref{escd})\\
{\tt ESC E}&edit a task's input parameters or a user-specified file
(\pageref{esce})\\
{\tt ESC G}&go to specified page number in \COA\ (\pageref{escg})\\
{\tt ESC H}&make hard copy of \COA\ (\pageref{esch})\\
{\tt ESC K}&pop up and down keyword display (\pageref{esck})\\
{\tt ESC L}&switch logging of integrated Unix shell on or off (\pageref{escl})\\
{\tt ESC M}&switch \UCA\ minimal matching on or off (\pageref{escm})\\
{\tt ESC P}&select printer for the {\tt ESC H} command (\pageref{escp})\\
{\tt ESC R}&(if keyword display active) write single task input parameter to
file (\pageref{escr})\\
{\tt ESC S}&change task or template context (\pageref{escs})\\
{\tt ESC T}&change task path (\pageref{esct})\\
{\tt ESC U}&pop up and down integrated Unix shell (\pageref{escu})\\
{\tt ESC W}&(if keyword display active) write all task's input parameters to
file (\pageref{escw})\\
{\tt ESC X}&activate or deactivate hypertext help (\pageref{escx})\\ 
{\tt ESC Z}&quit \tH\ (\pageref{escz})\\
{\tt ESC <}&go to first page of \COA \\
{\tt ESC >}&go to last page of \COA \\
{\tt ESC ESC}&circulate through task names in \UCA \\
{\tt ESC TAB}&switch user input keyword help display on or off\\
\end{tabular}

\begin{tabular}{ll}
{\bf ANSI keys}&\\
&\\
{$\uparrow$}&bring back previous commands to \UCA\ (backward direction)
(\pageref{uparrow})\\
{$\downarrow$}&bring back previous commands to \UCA\ (forward direction)
(\pageref{downarrow})\\
{$\leftarrow$}&move one position back in input line (={\tt CTRL-B})\\
{$\rightarrow$}&move one position forward in input line (={\tt CTRL-F})\\
{\tt PageUp}&page back in \COA, help display or keyword display
(={\tt CTRL-Z})\\
{\tt PageDown}&page forward in \COA, help display or keyword display
(={\tt CTRL-V})\\
\end{tabular}

\subsection*{Control characters}
\label{ctrlkeys}
\begin{tabular}{ll}
{\tt CTRL-A}&move to start of input line (\pageref{ctrla})\\
{\tt CTRL-B}&move one position back in input line (\pageref{ctrlb})\\
{\tt CTRL-C}&in \UCA : abort task; in transient window:
break out of window (\pageref{ctrlc})\\
{\tt CTRL-D}&forward delete character in input line (\pageref{ctrld})\\
{\tt CTRL-E}&move to end of input line (\pageref{ctrle})\\
{\tt CTRL-F}&move one position forward in input line (\pageref{ctrlf})\\
{\tt CTRL-G}&resume or suspend task (\pageref{ctrlg})\\
{\tt CTRL-H}&back delete one character\\
{\tt CTRL-I}&(={\tt TAB}) pop help screen up or down (\pageref{ctrli})\\
{\tt CTRL-J}&enter input line\\
{\tt CTRL-K}&forward delete rest of input line (\pageref{ctrlk})\\
{\tt CTRL-L}&rewrite terminal screen (\pageref{ctrll})\\
{\tt CTRL-M}&(={\tt RETURN}) enter input line\\
{\tt CTRL-N}&(undefined)\\
{\tt CTRL-O}&switch between windows accepting keyboard input (\pageref{ctrlo})\\
{\tt CTRL-P}&enter or leave \COA\ page mode (\pageref{ctrlp})\\
{\tt CTRL-Q}&quit \tH\ (\pageref{ctrlq})\\
{\tt CTRL-R}&reverse search in \COA\ or help display
(\pageref{ctrlr}, \pageref{ctrlrr})\\
{\tt CTRL-S}&forward search in \COA\ or help display
(\pageref{ctrls}, \pageref{ctrlss})\\
{\tt CTRL-T}&circulate through task names in \UCA\ (\pageref{ctrlt})\\
{\tt CTRL-U}&clear input line (\pageref{ctrlu})\\
{\tt CTRL-V}&page forward in \COA, help display or keyword display
(\pageref{ctrlv}, \pageref{ctrlvv}, \pageref{ctrlvvv})\\
{\tt CTRL-W}&suspend or resume task (\pageref{ctrlw})\\
{\tt CTRL-X}&initial character for special special combinations\\
{\tt CTRL-Y}&(undefined)\\
{\tt CTRL-Z}&page back in \COA, help display or keyword display
(\pageref{ctrlz}, \pageref{ctrlzz}, \pageref{ctrlzzz})\\
&\\
{\tt CTRL-X E}&edit user-specified file (\pageref{ctrlxe})\\
{\tt CTRL-X S}&switch \COA\ scroll mode on or off (\pageref{ctrlxs})\\
{\tt CTRL-X T}&switch Tektronix emulation on or off (\pageref{ctrlxt})\\
{\tt CTRL-X U}&overlay \H\ with shell (\pageref{ctrlxu})\\
{\tt CTRL-X CTRL-C}&quit \tH\ (\pageref{ctrlxctrlc})\\
\end{tabular}

(Numbers in parentheses refer to pages in this document.)
\chapter{Files used by tHermes}
\begin{tabular}{lll}
\hline
{\bf File name}&{\bf Usage}&{\bf Format}\\
\hline
{\tt GIPSY.LOG}&log file&text file\\
{\tt SCREEN.LOG}&terminal screen pages&internal\\
{\tt HERMES.keywd}&user input parameters (``macro'')&text file\\
{\tt HERMES.cmd}&previous commands (for command recall)&text file\\
{\tt *.def}&default file&text file\\
{\tt *.rcl}&recall file&text file\\
{\tt \$TEKNAME}&Tektronix graphics file (env. variable)&
{\tt PGPLOT} (``{\tt THERTEK}'')\\
{\it taskname\/\tt .key}&scratch file&\\
{\tt screen.prt}&scratch file&\\
{\tt keys.tmp}&scratch file&\\
\hline
\end{tabular}

The file {\tt GIPSY.LOG} contains the log of any number of \G\ sessions and
can be edited and printed. If the information contained by it is not necessary
anymore, it can be deleted.

{\tt SCREEN.LOG} contains the screen pages of any number of \G\
sessions. It cannot be edited or printed. Any modification of this file can
cause \tH\ to fail. If the information contained by it is not necessary
anymore, it can be deleted.

{\tt HERMES.keywd} contains all current user input parameters of
previously run tasks. This file can be edited, provided its format is
maintained. It would however be very seldomly necessary to edit this file.

{\tt HERMES.cmd} contains the last 1000 commands which were typed into
the \UCA. It is used for \tH' command recall feature.
This file can be edited, but this would be very seldomly necessary.

The files with the extension {\tt .def} are the user-defined default files.

The files with the extension {\tt .rcl} are the user-defined recall files.

The file to which the environment variable {\tt TEKNAME} points, contains
information to be displayed on a Tektronix~4010 compatible graphics
terminal. It can only be written by the {\tt PGPLOT} driver for the
``{\tt THERTEK}'' device.

The scratch files have normally all been cleaned up when \tH\ exits. If such
a file is present anyway, it can be deleted without any problem.

\end{document}
#<

#>            parse.l
%{
/* parse.l
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands


Hermes command line parser. The following functions are available:

   ParseCmd      -- parse a command line;
   CmdTaskName   -- obtain the task name as determined by ParseCmd;
   CmdExecName   -- obtain the name of the task executable;
   CmdMacro      -- obtain flag indicating whether to use previous keywords;
   CmdNkeys      -- obtain number of user input parameters;
   CmdParam      -- obtain user input parameters.

This module is private to Hermes.
Author: J.P. Terlouw.
*/

#define yylex parse_lex
#define ident pragma

#define  CMDEXTRA 30000       /* extended command length for large .def files */
#include "parse.h"
#include "stddef.h"
#include "ctype.h"
#include "alloc.h"
#undef REJECT
#include "taskcom.h"               /* only to define CMDLEN and KEYLEN */
#undef REJECT                      /* make sure new definition is not used */

#undef output                      /* do it here because output() is ... */
static void output(char);          /* ... referenced in the rules        */

#define USL (CMDLEN+CMDEXTRA)      /* unput() stack size: maximum cmnd length */
#undef  YYLMAX
#define YYLMAX USL                 /* redefine lex's maximum token length */
#define PATHLEN 50                 /* maximum executable path length */
#define TASKLEN 32                 /* maximum task name length */
#define EXECLEN (PATHLEN+TASKLEN)
#define VALALLOC 50                /* value string extension increment */

typedef struct _keyval {
   char* key;            /* keyword string */
   char* value;          /* value string */
   int   vallen;         /* (current) length of value string */
   int   bytesall;       /* number of bytes allocated for value string */
   struct _keyval *next; /* pointer to next keyval block */
} *keyval;

typedef struct _cmdinfo {
   int    nkeys;
   int    macroflag;
   char   tskname[TASKLEN];
   char   tskexec[EXECLEN];
   keyval kvlist;
   keyval currkv;
} _cmdinfo, *cmdinfo;

static int nkeys=0;
static int startkey=0;
static int taskfound=0;
static int pathfound=0;
static int errorcode=0;
static int macroflag=0;
static int aliasflag=0;
static char *bufptr;               /* input command buffer pointer */
static char ustack[USL];           /* stack for unput() */
static char tskpath[PATHLEN];
static char tskname[TASKLEN];
static char tskexec[EXECLEN];
static keyval kvlist=NULL, currkv=NULL;
static cmdinfo newinfo=NULL;
static int usp=0;

static void macro(void);
static void path(void);
static void taskname(void);
static void alias(void);
static void keyword(void);
%}

#if defined(stdin)
#undef stdin
#endif
#if defined(stdout)
#undef stdout
#endif
#define stdin  NULL
#define stdout NULL

%START MACRO PATH TASK ALIAS TAIL
%%
                               BEGIN MACRO;

<MACRO>!                       {
                                  macro();
                                  BEGIN PATH;
                               }

<MACRO,PATH>[\$./a-zA-Z0-9_]*\/  {
                                  path();
                                  BEGIN TASK;
                               }

<MACRO,PATH,TASK>[.a-zA-Z0-9_]+ {
                                  taskname();
                                  BEGIN ALIAS;
                               }

<MACRO,PATH,TASK>.             {
                                  if (!errorcode) errorcode = BADSYNTAX;
                               }

<ALIAS>\([\$./a-zA-Z0-9_]+\)       {
                                  alias();
                                  BEGIN TAIL;
                               }

[a-zA-Z0-9_]+=                  {
                                  keyword();
                                  BEGIN TAIL;
                               }

`.*`                           {
                                  int i;
                                  for (i=0; i<yyleng; i++) output(yytext[i]);
                               }

\t|\n                           output(' ');

\01                             BEGIN MACRO;       /* reset for next command */
%%

static void macro(void)        /* use-previous-keywords flag */
{
   macroflag++;
}

static void path(void)         /* task path */
{
   if (yyleng<PATHLEN) {
      strcpy(tskpath,yytext);
      pathfound++;
   } else {
      if (!errorcode) errorcode=PTOOLONG;
   }
}

static void taskname(void)    /* task name */
{
   char *c;
   int  i=0;

   if (yyleng<TASKLEN) {
      for (c = (char*)yytext; *c; c++) {
         tskname[i++] = islower(*c)?toupper(*c):*c;
      }
      tskname[i] = '\0';
      taskfound++;
   } else {
      if (!errorcode) errorcode=TTOOLONG;
   }
}

static void alias(void)      /* obtain executable name for alias */
{
   if (!pathfound) {
      aliasflag++;
      strcpy(tskexec,yytext+1);
      tskexec[yyleng-2] = 0;     /* parentheses are stripped off */
   } else {
      if (!errorcode) errorcode=PCONFLICT;
   }
}

static void keyword(void)        /* parameter keyword */
{
   keyval kvcurr;
   int    i;
   char   c;

   if (yyleng>=KEYLEN) {
      if (!errorcode) errorcode = KTOOLONG;
      return;
   }
   nkeys++;
   kvcurr = (keyval)Calloc(1,sizeof(struct _keyval));
   kvcurr->next = kvlist;
   kvlist = kvcurr;
   kvlist->key = Malloc(yyleng+1);
   kvlist->value = Calloc(VALALLOC,1);
   kvlist->bytesall = VALALLOC;
   for (i=0; i<=yyleng; i++) {
      c = yytext[i];
      kvlist->key[i] = islower(c)?toupper(c):c;
   }
   startkey = 1;
}

static void output(char c)          /* lex output substitute */
{
   if (nkeys) {                                      /* any keyword found? */
      if (startkey) {
         if (c==' ') return;                         /* ignore leading blanks */
      }
      startkey = 0;
      /* enough room for character plus trailing zero ? */
      if (kvlist->vallen > kvlist->bytesall-2) {
         kvlist->bytesall += VALALLOC;
         kvlist->value = Realloc(kvlist->value,kvlist->bytesall);
      }
      kvlist->value[kvlist->vallen++] = c;
   }
}

#undef input
static char input(void)              /* lex input substitute */
{
   static errnext = 0;

   if (usp) {
      return ustack[--usp];          /* first return any un-put character */
   } else {
      if (errnext) {                 /* second phase after error or e-o-c  */
         errnext = 0;
         return '\00';
      }
      if (errorcode || !*(bufptr)) { /* error or end-of-command */
         errnext = 1;
         return '\01';               /* stop the machine  */
      }
      else return *(bufptr++);       /* then characters from the buffer   */
   }
}

#undef unput
static void unput(char c)            /* lex unput substitute */
{
   if (usp<USL) {
      ustack[usp++] = c;             /* stack the character */
   } else {
      fprintf(stderr,"Too many unputs\n");   /* this should not happen ... */
      exit(1);
   }
}

int yywrap(void)
{
   int i,pl;

   usp = 0;
   startkey=0;
   if (!aliasflag) {                   /* no alias: build executable filename */
      *tskexec = 0;
      if (pathfound) {
         strcpy(tskexec,tskpath);
      }
      pl = strlen(tskexec);
      for (i=0; tskname[i]; i++) {
         /* standard executables: always lowercase names */
         tskexec[i+pl] = isupper(tskname[i])?tolower(tskname[i]):tskname[i];
      }
      tskexec[i+pl] = 0;
   }
   return 1;
}

/*
 *  ParseCmd parses a GIPSY command line. It only returns an error status.
 *  Information resulting from parsing a command line is stored internally
 *  and can be obtained by calling the appropriate functions.
 *  Currently functions CmdParam, CmdMacro, CmdTaskName and CmdExecName
 *  are available.
 */
extern int ParseCmd(char *cmd)
{
   startkey  = 0;
   taskfound = 0;
   pathfound = 0;
   errorcode = 0;
   macroflag = 0;
   aliasflag = 0;
   usp       = 0;
   bufptr    = cmd;
   nkeys=0;

   if (newinfo) CmdDelete((long)newinfo);     /* delete any 'unclaimed' struct */
   newinfo = New(_cmdinfo);

   currkv = NULL;
   kvlist = NULL;
   if (strlen(cmd)>YYLMAX) errorcode = CTOOLONG; else yylex();
   if (!errorcode && taskfound==0) errorcode = NOTSKNAME;
   if (!errorcode || errorcode==NOTSKNAME) {
      currkv = kvlist;                           /* start value for nextkey() */
   } else {
      currkv = NULL;
   }
   newinfo->nkeys = nkeys;
   newinfo->macroflag = macroflag;
   strcpy(newinfo->tskname,tskname);
   strcpy(newinfo->tskexec,tskexec);
   newinfo->kvlist = kvlist;
   newinfo->currkv = currkv;
   return errorcode;
}

/*
 *  CmdGetInfo returns a "command information id". This value is to be used
 *  in calls to other entries in this module. It must eventually be
 *  deleted using CmdDelete.
 */
extern long	CmdGetInfo(void)
{
   long result=(long)newinfo;
   newinfo = NULL;
   return result;
}

/*
 *  CmdParam returns the next keyword/value pair via its arguments.
 *  The arguments are pointers to pointers to char, so in the call the
 *  address of a character pointer must be specified.
 *  If the keyword/value list is exhausted, NULL pointers are returned.
 */
extern void CmdParam(long cmdid, char **key, char **value)
{
   currkv = ((cmdinfo)cmdid)->currkv;
   if (currkv) {
      *key = currkv->key;
      currkv->value[currkv->vallen] = 0;
      while (currkv->vallen && currkv->value[--currkv->vallen] == ' ') {
         currkv->value[currkv->vallen] = 0;    /* remove trailing blanks */
      }
      *value = currkv->value;
      currkv = currkv->next;
   } else {
      *key = NULL;
      *value = NULL;
   }
   ((cmdinfo)cmdid)->currkv = currkv;
}

/*
 *  CmdMacro returns a flag indicating whether an exclamation sign was
 *  present in the first position of the command.
 */
extern int CmdMacro(long cmdid)
{
   return ((cmdinfo)cmdid)->macroflag;
}

/*
 *  CmdNkeys returns the number of keyword/value pairs found in the command.
 */
extern int CmdNkeys(long cmdid)
{
   return ((cmdinfo)cmdid)->nkeys;
}

/*
 *  CmdTaskName returns a pointer to the taskname string found in the command.
 */
extern char* CmdTaskName(long cmdid)
{
   return ((cmdinfo)cmdid)->tskname;
}

/*
 *  CmdExecName returns a pointer to a string containing the executable
 *  file name.
 */
extern char* CmdExecName(long cmdid)
{
   return ((cmdinfo)cmdid)->tskexec;
}

/*
 *  CmdErrString returns a pointer to a string containing an explanation
 *  of the last error code.
 */
extern char* CmdErrString(int code)
{
   static char badsyntax[]="BAD SYNTAX";
   static char ptoolong[]="PATH TOO LONG";
   static char ttoolong[]="TASKNAME TOO LONG";
   static char pconflict[]="SYNTAX ERROR IN ALIAS";
   static char notskname[]="TASKNAME MISSING";
   static char ctoolong[]="COMMAND TOO LONG";
   static char ktoolong[]="KEYWORD TOO LONG";

   if (!code) code=errorcode;
   switch (code) {
      case BADSYNTAX: return badsyntax;
      case PTOOLONG: return ptoolong;
      case TTOOLONG: return ttoolong;
      case PCONFLICT: return pconflict;
      case NOTSKNAME: return notskname;
      case CTOOLONG: return ctoolong;
      case KTOOLONG: return ktoolong;
      default: return "UNDOCUMENTED SYNTAX ERROR";
   }
}

extern void CmdDelete(long cmdid)
{
   cmdinfo info=(cmdinfo)cmdid;

   kvlist = info->kvlist;
   for (currkv = kvlist; currkv; currkv = currkv->next) {
      free(currkv->key);
      free(currkv->value);
   }                                      /* clean up keyword/value strings */
   while (kvlist) {
      currkv = kvlist;
      kvlist = currkv->next;
      free(currkv);
   }                                      /* clean up keyval structures */
   Delete(info);                          /* clean up info struct */
}
#<

#>            parse.c
# include "stdio.h"
#if defined(__cplusplus)
   extern "C" {
#endif
#if (defined(__cplusplus) || defined(__STDC__))
     extern int yyreject();
     extern int yywrap();
     extern int yylook();
     extern int yyback(int *, int);
     extern int yyinput();
     extern void yyoutput(int);
     extern void yyunput(int);
     extern int yylex();
     extern int yyless(int);
#ifdef LEXDEBUG
     extern void allprint();
     extern void sprint();
#endif
#if defined(__cplusplus)
   }
#endif
#endif	/* __cplusplus or __STDC__ */
# define U(x) x
# define NLSTATE yyprevious=YYNEWLINE
# define BEGIN yybgin = yysvec + 1 +
# define INITIAL 0
# define YYLERR yysvec
# define YYSTATE (yyestate-yysvec-1)
# define YYOPTIM 1
# define YYLMAX 200
# define output(c) putc(c,yyout)
# define input() (((yytchar=yysptr>yysbuf?U(*--yysptr):getc(yyin))==10?(yylineno++,yytchar):yytchar)==EOF?0:yytchar)
# define unput(c) {yytchar= (c);if(yytchar=='\n')yylineno--;*yysptr++=yytchar;}
# define yymore() (yymorfg=1)
# define ECHO fprintf(yyout, "%s",yytext)
# define REJECT { nstr = yyreject(); goto yyfussy;}
int yyleng;
int yylenguc;
extern unsigned char yytextarr[];
# ifdef YYCHAR_ARRAY
extern char yytext[];
# else
extern unsigned char yytext[];
# endif
int yyposix_point=0;
int yynls16=0;
int yynls_wchar=0;
char *yylocale = "C C C C C C";
int yymorfg;
extern unsigned char *yysptr, yysbuf[];
int yytchar;
FILE *yyin = {NULL}, *yyout = {NULL};
extern int yylineno;
struct yysvf { 
	int yystoff;
	struct yysvf *yyother;
	int *yystops;};
struct yysvf *yyestate;
extern struct yysvf yysvec[], *yybgin;
/* parse.l
                              COPYRIGHT (c) 1991
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands


Hermes command line parser. The following functions are available:

   ParseCmd      -- parse a command line;
   CmdTaskName   -- obtain the task name as determined by ParseCmd;
   CmdExecName   -- obtain the name of the task executable;
   CmdMacro      -- obtain flag indicating whether to use previous keywords;
   CmdNkeys      -- obtain number of user input parameters;
   CmdParam      -- obtain user input parameters.

This module is private to Hermes.
Author: J.P. Terlouw.
*/

#define yylex parse_lex
#define ident pragma

#define  CMDEXTRA 30000       /* extended command length for large .def files */
#include "parse.h"
#include "stddef.h"
#include "ctype.h"
#include "alloc.h"
#undef REJECT
#include "taskcom.h"               /* only to define CMDLEN and KEYLEN */
#undef REJECT                      /* make sure new definition is not used */

#undef output                      /* do it here because output() is ... */
static void output(char);          /* ... referenced in the rules        */

#define USL (CMDLEN+CMDEXTRA)      /* unput() stack size: maximum cmnd length */
#undef  YYLMAX
#define YYLMAX USL                 /* redefine lex's maximum token length */
#define PATHLEN 50                 /* maximum executable path length */
#define TASKLEN 32                 /* maximum task name length */
#define EXECLEN (PATHLEN+TASKLEN)
#define VALALLOC 50                /* value string extension increment */

typedef struct _keyval {
   char* key;            /* keyword string */
   char* value;          /* value string */
   int   vallen;         /* (current) length of value string */
   int   bytesall;       /* number of bytes allocated for value string */
   struct _keyval *next; /* pointer to next keyval block */
} *keyval;

typedef struct _cmdinfo {
   int    nkeys;
   int    macroflag;
   char   tskname[TASKLEN];
   char   tskexec[EXECLEN];
   keyval kvlist;
   keyval currkv;
} _cmdinfo, *cmdinfo;

static int nkeys=0;
static int startkey=0;
static int taskfound=0;
static int pathfound=0;
static int errorcode=0;
static int macroflag=0;
static int aliasflag=0;
static char *bufptr;               /* input command buffer pointer */
static char ustack[USL];           /* stack for unput() */
static char tskpath[PATHLEN];
static char tskname[TASKLEN];
static char tskexec[EXECLEN];
static keyval kvlist=NULL, currkv=NULL;
static cmdinfo newinfo=NULL;
static int usp=0;

static void macro(void);
static void path(void);
static void taskname(void);
static void alias(void);
static void keyword(void);
# define MACRO 2
# define PATH 4
# define TASK 6
# define ALIAS 8
# define TAIL 10
# define YYNEWLINE 10
yylex(){
   int nstr; extern int yyprevious;
                              BEGIN MACRO;

   while((nstr = yylook()) >= 0)
yyfussy: switch(nstr){
case 0:
   if(yywrap()) return(0); break;
case 1:
                      {
                                  macro();
                                  BEGIN PATH;
                               }
break;
case 2:
 {
                                  path();
                                  BEGIN TASK;
                               }
break;
case 3:
{
                                  taskname();
                                  BEGIN ALIAS;
                               }
break;
case 4:
            {
                                  if (!errorcode) errorcode = BADSYNTAX;
                               }
break;
case 5:
      {
                                  alias();
                                  BEGIN TAIL;
                               }
break;
case 6:
                 {
                                  keyword();
                                  BEGIN TAIL;
                               }
break;
case 7:
                          {
                                  int i;
                                  for (i=0; i<yyleng; i++) output(yytext[i]);
                               }
break;
case 8:
                          output(' ');
break;
case 9:
                            BEGIN MACRO;
break;
case -1:
break;
default:
   fprintf(yyout,"bad switch yylook %d",nstr);
} return(0); }
/* end of yylex */

#ifndef __cplusplus
static void __yy__unused() { main(); }
#endif

static void macro(void)        /* use-previous-keywords flag */
{
   macroflag++;
}

static void path(void)         /* task path */
{
   if (yyleng<PATHLEN) {
      strcpy(tskpath,yytext);
      pathfound++;
   } else {
      if (!errorcode) errorcode=PTOOLONG;
   }
}

static void taskname(void)    /* task name */
{
   char *c;
   int  i=0;

   if (yyleng<TASKLEN) {
      for (c = (char*)yytext; *c; c++) {
         tskname[i++] = islower(*c)?toupper(*c):*c;
      }
      tskname[i] = '\0';
      taskfound++;
   } else {
      if (!errorcode) errorcode=TTOOLONG;
   }
}

static void alias(void)      /* obtain executable name for alias */
{
   if (!pathfound) {
      aliasflag++;
      strcpy(tskexec,yytext+1);
      tskexec[yyleng-2] = 0;     /* parentheses are stripped off */
   } else {
      if (!errorcode) errorcode=PCONFLICT;
   }
}

static void keyword(void)        /* parameter keyword */
{
   keyval kvcurr;
   int    i;
   char   c;

   if (yyleng>=KEYLEN) {
      if (!errorcode) errorcode = KTOOLONG;
      return;
   }
   nkeys++;
   kvcurr = (keyval)Calloc(1,sizeof(struct _keyval));
   kvcurr->next = kvlist;
   kvlist = kvcurr;
   kvlist->key = Malloc(yyleng+1);
   kvlist->value = Calloc(VALALLOC,1);
   kvlist->bytesall = VALALLOC;
   for (i=0; i<=yyleng; i++) {
      c = yytext[i];
      kvlist->key[i] = islower(c)?toupper(c):c;
   }
   startkey = 1;
}

static void output(char c)          /* lex output substitute */
{
   if (nkeys) {                                      /* any keyword found? */
      if (startkey) {
         if (c==' ') return;                         /* ignore leading blanks */
      }
      startkey = 0;
      /* enough room for character plus trailing zero ? */
      if (kvlist->vallen > kvlist->bytesall-2) {
         kvlist->bytesall += VALALLOC;
         kvlist->value = Realloc(kvlist->value,kvlist->bytesall);
      }
      kvlist->value[kvlist->vallen++] = c;
   }
}

#undef input
static char input(void)              /* lex input substitute */
{
   static errnext = 0;

   if (usp) {
      return ustack[--usp];          /* first return any un-put character */
   } else {
      if (errnext) {                 /* second phase after error or e-o-c  */
         errnext = 0;
         return '\00';
      }
      if (errorcode || !*(bufptr)) { /* error or end-of-command */
         errnext = 1;
         return '\01';               /* stop the machine  */
      }
      else return *(bufptr++);       /* then characters from the buffer   */
   }
}

#undef unput
static void unput(char c)            /* lex unput substitute */
{
   if (usp<USL) {
      ustack[usp++] = c;             /* stack the character */
   } else {
      fprintf(stderr,"Too many unputs\n");   /* this should not happen ... */
      exit(1);
   }
}

int yywrap(void)
{
   int i,pl;

   usp = 0;
   startkey=0;
   if (!aliasflag) {                   /* no alias: build executable filename */
      *tskexec = 0;
      if (pathfound) {
         strcpy(tskexec,tskpath);
      }
      pl = strlen(tskexec);
      for (i=0; tskname[i]; i++) {
         /* standard executables: always lowercase names */
         tskexec[i+pl] = isupper(tskname[i])?tolower(tskname[i]):tskname[i];
      }
      tskexec[i+pl] = 0;
   }
   return 1;
}

/*
 *  ParseCmd parses a GIPSY command line. It only returns an error status.
 *  Information resulting from parsing a command line is stored internally
 *  and can be obtained by calling the appropriate functions.
 *  Currently functions CmdParam, CmdMacro, CmdTaskName and CmdExecName
 *  are available.
 */
extern int ParseCmd(char *cmd)
{
   startkey  = 0;
   taskfound = 0;
   pathfound = 0;
   errorcode = 0;
   macroflag = 0;
   aliasflag = 0;
   usp       = 0;
   bufptr    = cmd;
   nkeys=0;

   if (newinfo) CmdDelete((long)newinfo);     /* delete any 'unclaimed' struct */
   newinfo = New(_cmdinfo);

   currkv = NULL;
   kvlist = NULL;
   if (strlen(cmd)>YYLMAX) errorcode = CTOOLONG; else yylex();
   if (!errorcode && taskfound==0) errorcode = NOTSKNAME;
   if (!errorcode || errorcode==NOTSKNAME) {
      currkv = kvlist;                           /* start value for nextkey() */
   } else {
      currkv = NULL;
   }
   newinfo->nkeys = nkeys;
   newinfo->macroflag = macroflag;
   strcpy(newinfo->tskname,tskname);
   strcpy(newinfo->tskexec,tskexec);
   newinfo->kvlist = kvlist;
   newinfo->currkv = currkv;
   return errorcode;
}

/*
 *  CmdGetInfo returns a "command information id". This value is to be used
 *  in calls to other entries in this module. It must eventually be
 *  deleted using CmdDelete.
 */
extern long	CmdGetInfo(void)
{
   long result=(long)newinfo;
   newinfo = NULL;
   return result;
}

/*
 *  CmdParam returns the next keyword/value pair via its arguments.
 *  The arguments are pointers to pointers to char, so in the call the
 *  address of a character pointer must be specified.
 *  If the keyword/value list is exhausted, NULL pointers are returned.
 */
extern void CmdParam(long cmdid, char **key, char **value)
{
   currkv = ((cmdinfo)cmdid)->currkv;
   if (currkv) {
      *key = currkv->key;
      currkv->value[currkv->vallen] = 0;
      while (currkv->vallen && currkv->value[--currkv->vallen] == ' ') {
         currkv->value[currkv->vallen] = 0;    /* remove trailing blanks */
      }
      *value = currkv->value;
      currkv = currkv->next;
   } else {
      *key = NULL;
      *value = NULL;
   }
   ((cmdinfo)cmdid)->currkv = currkv;
}

/*
 *  CmdMacro returns a flag indicating whether an exclamation sign was
 *  present in the first position of the command.
 */
extern int CmdMacro(long cmdid)
{
   return ((cmdinfo)cmdid)->macroflag;
}

/*
 *  CmdNkeys returns the number of keyword/value pairs found in the command.
 */
extern int CmdNkeys(long cmdid)
{
   return ((cmdinfo)cmdid)->nkeys;
}

/*
 *  CmdTaskName returns a pointer to the taskname string found in the command.
 */
extern char* CmdTaskName(long cmdid)
{
   return ((cmdinfo)cmdid)->tskname;
}

/*
 *  CmdExecName returns a pointer to a string containing the executable
 *  file name.
 */
extern char* CmdExecName(long cmdid)
{
   return ((cmdinfo)cmdid)->tskexec;
}

/*
 *  CmdErrString returns a pointer to a string containing an explanation
 *  of the last error code.
 */
extern char* CmdErrString(int code)
{
   static char badsyntax[]="BAD SYNTAX";
   static char ptoolong[]="PATH TOO LONG";
   static char ttoolong[]="TASKNAME TOO LONG";
   static char pconflict[]="SYNTAX ERROR IN ALIAS";
   static char notskname[]="TASKNAME MISSING";
   static char ctoolong[]="COMMAND TOO LONG";
   static char ktoolong[]="KEYWORD TOO LONG";

   if (!code) code=errorcode;
   switch (code) {
      case BADSYNTAX: return badsyntax;
      case PTOOLONG: return ptoolong;
      case TTOOLONG: return ttoolong;
      case PCONFLICT: return pconflict;
      case NOTSKNAME: return notskname;
      case CTOOLONG: return ctoolong;
      case KTOOLONG: return ktoolong;
      default: return "UNDOCUMENTED SYNTAX ERROR";
   }
}

extern void CmdDelete(long cmdid)
{
   cmdinfo info=(cmdinfo)cmdid;

   kvlist = info->kvlist;
   for (currkv = kvlist; currkv; currkv = currkv->next) {
      free(currkv->key);
      free(currkv->value);
   }                                      /* clean up keyword/value strings */
   while (kvlist) {
      currkv = kvlist;
      kvlist = currkv->next;
      free(currkv);
   }                                      /* clean up keyval structures */
   Delete(info);                          /* clean up info struct */
}
int yyvstop[] = {
0,

9,
0,

8,
0,

4,
9,
0,

4,
0,

4,
8,
0,

1,
4,
0,

4,
0,

3,
4,
0,

2,
4,
0,

3,
4,
0,

4,
0,

3,
4,
0,

3,
4,
0,

6,
0,

7,
0,

2,
0,

3,
0,

3,
0,

3,
0,

3,
0,

5,
0,
0};
# define YYTYPE unsigned char
struct yywork { YYTYPE verify, advance; } yycrank[] = {
{0,0},	{0,0},	{1,13},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{1,14},	{1,14},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{0,0},	{0,0},	{0,0},	{0,0},	
{1,15},	{1,16},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{1,15},	{1,15},	{1,15},	{1,15},	
{3,17},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,19},	{3,14},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,20},	{3,18},	{3,18},	{3,21},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,22},	{3,23},	{3,24},	
{9,28},	{10,28},	{37,38},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{3,18},	{3,18},	
{3,18},	{3,18},	{0,0},	{3,25},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{3,18},	
{3,18},	{3,18},	{3,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,19},	{0,0},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,20},	{4,18},	
{4,18},	{0,0},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{22,33},	
{4,23},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{4,18},	{4,18},	{4,18},	{4,18},	
{22,33},	{4,25},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{22,33},	{22,33},	{22,33},	{22,33},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{4,18},	{4,18},	{4,18},	
{4,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,19},	{0,0},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{25,0},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{0,0},	{5,23},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{5,18},	{5,18},	
{5,18},	{5,18},	{0,0},	{5,25},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{25,30},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{5,18},	
{5,18},	{5,18},	{5,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,19},	{0,0},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{0,0},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{26,35},	
{6,23},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{6,18},	{6,18},	{6,18},	{6,18},	
{26,35},	{6,25},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{26,35},	{26,35},	{26,35},	{26,35},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{6,18},	{6,18},	{6,18},	
{6,18},	{7,17},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,19},	{7,14},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,26},	{0,0},	
{7,27},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{7,18},	
{7,18},	{7,18},	{7,18},	{0,0},	
{7,25},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{7,18},	{7,18},	{7,18},	{7,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,19},	
{0,0},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{21,31},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{21,31},	{21,32},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{8,18},	{8,18},	{8,18},	
{8,18},	{21,31},	{8,25},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{21,31},	{21,31},	{21,31},	
{21,31},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{8,18},	{8,18},	
{8,18},	{8,18},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{30,0},	{0,0},	{0,0},	{15,29},	
{0,0},	{0,0},	{0,0},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{0,0},	{0,0},	{0,0},	
{0,0},	{15,15},	{0,0},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{15,15},	{15,15},	{15,15},	
{15,15},	{16,16},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{16,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{30,30},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{16,16},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{16,16},	{0,0},	
{16,16},	{24,33},	{0,0},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{0,0},	{0,0},	{0,0},	
{24,29},	{0,0},	{0,0},	{0,0},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{0,0},	{0,0},	
{16,30},	{0,0},	{24,34},	{0,0},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{24,34},	{24,34},	
{24,34},	{24,34},	{27,35},	{0,0},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{0,0},	{0,0},	
{0,0},	{27,29},	{0,0},	{0,0},	
{0,0},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{0,0},	
{0,0},	{0,0},	{0,0},	{27,36},	
{0,0},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{27,36},	
{27,36},	{27,36},	{27,36},	{28,37},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{0,0},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{0,0},	{0,0},	
{0,0},	{0,0},	{28,37},	{0,0},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{28,37},	{28,37},	
{28,37},	{28,37},	{33,33},	{0,0},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{0,0},	{0,0},	
{0,0},	{0,0},	{0,0},	{0,0},	
{0,0},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{0,0},	
{0,0},	{0,0},	{0,0},	{33,33},	
{0,0},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{33,33},	
{33,33},	{33,33},	{33,33},	{34,33},	
{0,0},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{0,0},	
{0,0},	{0,0},	{34,29},	{0,0},	
{0,0},	{0,0},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{0,0},	{0,0},	{0,0},	{0,0},	
{34,34},	{0,0},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{34,34},	{34,34},	{34,34},	{34,34},	
{0,0}};
struct yysvf yysvec[] = {
{0,	0,	0},
{1,	0,		0},	
{0,	yysvec+1,	0},	
{-123,	0,		0},	
{-377,	yysvec+3,	0},	
{-631,	yysvec+3,	0},	
{-885,	yysvec+3,	0},	
{-1140,	0,		0},	
{-1394,	yysvec+7,	0},	
{132,	yysvec+1,	0},	
{133,	yysvec+1,	0},	
{0,	yysvec+1,	0},	
{0,	yysvec+1,	0},	
{0,	0,		yyvstop+1},
{0,	0,		yyvstop+3},
{1602,	0,		0},	
{-1724,	0,		0},	
{0,	0,		yyvstop+5},
{0,	0,		yyvstop+8},
{0,	0,		yyvstop+10},
{0,	0,		yyvstop+13},
{1394,	0,		yyvstop+16},
{377,	yysvec+21,	yyvstop+18},
{0,	yysvec+21,	yyvstop+21},
{1727,	yysvec+21,	yyvstop+24},
{-657,	yysvec+16,	yyvstop+27},
{885,	0,		yyvstop+29},
{1804,	0,		yyvstop+32},
{1891,	0,		0},	
{0,	0,		yyvstop+35},
{-1650,	yysvec+16,	yyvstop+37},
{0,	yysvec+21,	0},	
{0,	yysvec+21,	yyvstop+39},
{1968,	yysvec+21,	yyvstop+41},
{2045,	yysvec+21,	yyvstop+43},
{0,	yysvec+26,	yyvstop+45},
{0,	yysvec+27,	yyvstop+47},
{133,	yysvec+28,	0},	
{0,	0,		yyvstop+49},
{0,	0,	0}};
struct yywork *yytop = yycrank+2167;
struct yysvf *yybgin = yysvec+1;
unsigned char yymatch[] = {
00  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,012 ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,'$' ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,'.' ,'$' ,
'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,
'0' ,'0' ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,
'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,
'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,
'0' ,'0' ,'0' ,01  ,01  ,01  ,01  ,'0' ,
01  ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,
'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,
'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,'0' ,
'0' ,'0' ,'0' ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
01  ,01  ,01  ,01  ,01  ,01  ,01  ,01  ,
0};
unsigned char yyextra[] = {
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0};
/* @(#) A.10.32.03 HP C LANGUAGE TOOL (NCFORM) 960517 $      */
int yylineno =1;
# define YYU(x) x
# define NLSTATE yyprevious=YYNEWLINE
 
#ifdef YYNLS16_WCHAR
unsigned char yytextuc[YYLMAX * sizeof(wchar_t)];
# ifdef YY_PCT_POINT /* for %pointer */
wchar_t yytextarr[YYLMAX];
wchar_t *yytext;
# else               /* %array */
wchar_t yytextarr[1];
wchar_t yytext[YYLMAX];
# endif
#else
unsigned char yytextuc;
# ifdef YY_PCT_POINT /* for %pointer */
unsigned char yytextarr[YYLMAX];
unsigned char *yytext;
# else               /* %array */
unsigned char yytextarr[1];
# ifdef YYCHAR_ARRAY
char yytext[YYLMAX];
# else
unsigned char yytext[YYLMAX];
# endif
# endif
#endif

struct yysvf *yylstate [YYLMAX], **yylsp, **yyolsp;
unsigned char yysbuf[YYLMAX];
unsigned char *yysptr = yysbuf;
int *yyfnd;
extern struct yysvf *yyestate;
int yyprevious = YYNEWLINE;
yylook(){
	register struct yysvf *yystate, **lsp;
	register struct yywork *yyt;
	struct yysvf *yyz;
	int yych, yyfirst;
	struct yywork *yyr;
# ifdef LEXDEBUG
	int debug;
# endif
/*	char *yylastch;
 * ***** nls8 ***** */
	unsigned char *yylastch, sec, third, fourth;
	/* start off machines */
# ifdef LEXDEBUG
	debug = 0;
# endif
	yyfirst=1;
	if (!yymorfg)
#ifdef YYNLS16_WCHAR
		yylastch = yytextuc;
#else
# ifdef YYCHAR_ARRAY
		yylastch = (unsigned char *)yytext;
# else
		yylastch = yytext;
# endif
#endif
	else {
		yymorfg=0;
#ifdef YYNLS16_WCHAR
		yylastch = yytextuc+yylenguc;
#else
# ifdef YYCHAR_ARRAY
		yylastch = (unsigned char *)yytext+yyleng;
# else
		yylastch = yytext+yyleng;
# endif
#endif
		}
	for(;;){
		lsp = yylstate;
		yyestate = yystate = yybgin;
		if (yyprevious==YYNEWLINE) yystate++;
		for (;;){
# ifdef LEXDEBUG
			if(debug)fprintf(yyout,"state %d\n",yystate-yysvec-1);
# endif
			yyt = &yycrank[yystate->yystoff];
			if(yyt == yycrank && !yyfirst){  /* may not be any transitions */
				yyz = yystate->yyother;
				if(yyz == 0)break;
				if(yyz->yystoff == 0)break;
				}
			*yylastch++ = yych = input();
			yyfirst=0;
		tryagain:
# ifdef LEXDEBUG
			if(debug){
				fprintf(yyout,"char ");
				allprint(yych);
				putchar('\n');
				}
# endif
			yyr = yyt;
			if ( (int)yyt > (int)yycrank){
				yyt = yyr + yych;
				if (yyt <= yytop && yyt->verify+yysvec == yystate){
					if(yyt->advance+yysvec == YYLERR)	/* error transitions */
						{unput(*--yylastch);break;}
					*lsp++ = yystate = yyt->advance+yysvec;
					goto contin;
					}
				}
# ifdef YYOPTIM
			else if((int)yyt < (int)yycrank) {		/* r < yycrank */
				yyt = yyr = yycrank+(yycrank-yyt);
# ifdef LEXDEBUG
				if(debug)fprintf(yyout,"compressed state\n");
# endif
				yyt = yyt + yych;
				if(yyt <= yytop && yyt->verify+yysvec == yystate){
					if(yyt->advance+yysvec == YYLERR)	/* error transitions */
						{unput(*--yylastch);break;}
					*lsp++ = yystate = yyt->advance+yysvec;
					goto contin;
					}
				yyt = yyr + YYU(yymatch[yych]);
# ifdef LEXDEBUG
				if(debug){
					fprintf(yyout,"try fall back character ");
					allprint(YYU(yymatch[yych]));
					putchar('\n');
					}
# endif
				if(yyt <= yytop && yyt->verify+yysvec == yystate){
					if(yyt->advance+yysvec == YYLERR)	/* error transition */
						{unput(*--yylastch);break;}
					*lsp++ = yystate = yyt->advance+yysvec;
					goto contin;
					}
				}
			if ((yystate = yystate->yyother) && (yyt = &yycrank[yystate->yystoff]) != yycrank){
# ifdef LEXDEBUG
				if(debug)fprintf(yyout,"fall back to state %d\n",yystate-yysvec-1);
# endif
				goto tryagain;
				}
# endif
			else
				{unput(*--yylastch);break;}
		contin:
# ifdef LEXDEBUG
			if(debug){
				fprintf(yyout,"state %d char ",yystate-yysvec-1);
				allprint(yych);
				putchar('\n');
				}
# endif
			;
			}
# ifdef LEXDEBUG
		if(debug){
			fprintf(yyout,"stopped at %d with ",*(lsp-1)-yysvec-1);
			allprint(yych);
			putchar('\n');
			}
# endif
		while (lsp-- > yylstate){
			*yylastch-- = 0;
			if (*lsp != 0 && (yyfnd= (*lsp)->yystops) && *yyfnd > 0){
				yyolsp = lsp;
				if(yyextra[*yyfnd]){		/* must backup */
					while(yyback((*lsp)->yystops,-*yyfnd) != 1 && lsp > yylstate){
						lsp--;
						unput(*yylastch--);
						}
					}
				yyprevious = YYU(*yylastch);
				yylsp = lsp;
#ifdef YYNLS16_WCHAR
				yylenguc = yylastch-yytextuc+1;
				yytextuc[yylenguc] = 0;
#else
# ifdef YYCHAR_ARRAY
				yyleng = yylastch-(unsigned char*)yytext+1;
# else
				yyleng = yylastch-yytext+1;
# endif
				yytext[yyleng] = 0;
#endif
# ifdef LEXDEBUG
				if(debug){
					fprintf(yyout,"\nmatch ");
#ifdef YYNLS16_WCHAR
					sprint(yytextuc);
#else
					sprint(yytext);
#endif
					fprintf(yyout," action %d\n",*yyfnd);
					}
# endif
				return(*yyfnd++);
				}
			unput(*yylastch);
			}
#ifdef YYNLS16_WCHAR
		if (yytextuc[0] == 0  /* && feof(yyin) */)
#else
		if (yytext[0] == 0  /* && feof(yyin) */)
#endif
			{
			yysptr=yysbuf;
			return(0);
			}
#ifdef YYNLS16_WCHAR
		yyprevious = yytextuc[0] = input();
#else
		yyprevious = yytext[0] = input();
#endif
		if (yyprevious>0) {
			output(yyprevious);
#ifdef YYNLS16
                        if (yynls16) {
			int noBytes;
                        sec = input();
                        third = input();
                        fourth = input();
#ifdef YYNLS16_WCHAR
                        noBytes = MultiByte(yytextuc[0],sec,third,fourth);
#else 
                        noBytes = MultiByte(yytext[0],sec,third,fourth);
#endif          
     					switch(noBytes) {
     					case 2:
#ifdef YYNLS16_WCHAR
 						output(yyprevious=yytextuc[0]=sec);
#else
 						output(yyprevious=yytext[0]=sec);
#endif
                                                 unput(fourth);
                                                 unput(third);
                                                 break;
     					case 3:
#ifdef YYNLS16_WCHAR
 						output(yyprevious=yytextuc[0]=sec);
 						output(yyprevious=yytextuc[0]=third);
#else
 						output(yyprevious=yytext[0]=sec);
 						output(yyprevious=yytext[0]=third);
#endif
                                                 unput(fourth);
                                                 break; 
                                         case 4:
#ifdef YYNLS16_WCHAR
 						output(yyprevious=yytextuc[0]=sec);
 						output(yyprevious=yytextuc[0]=third);
 						output(yyprevious=yytextuc[0]=fourth);
#else
 						output(yyprevious=yytext[0]=sec);
 						output(yyprevious=yytext[0]=third);
 						output(yyprevious=yytext[0]=fourth);
#endif
                                                 break;                                                                                            
					default:
					        unput(fourth);
					        unput(third);
						unput(sec);
						break;
						}
					}
#endif
                }
#ifdef YYNLS16_WCHAR
		yylastch=yytextuc;
#else
# ifdef YYCHAR_ARRAY
		yylastch=(unsigned char*)yytext;
# else
		yylastch=yytext;
# endif
#endif
# ifdef LEXDEBUG
		if(debug)putchar('\n');
# endif
		}
	}

# ifdef __cplusplus
yyback(int *p, int m)
# else
yyback(p, m)
	int *p;
# endif
{
if (p==0) return(0);
while (*p)
	{
	if (*p++ == m)
		return(1);
	}
return(0);
}
	/* the following are only used in the lex library */
yyinput(){
	return(input());
	
	}

#if (defined(__cplusplus) || defined(__STDC__))
void yyoutput(int c)
#else
yyoutput(c)
  int c;
# endif
{
	output(c);
}

#if (defined(__cplusplus) || defined(__STDC__))
void yyunput(int c)
#else
yyunput(c)
   int c;
#endif
{
	unput(c);
}
#<
