
/*******************************************************************************

 © R.J.Appleton                                30th July 1989
   14 Star Post Road
   CAMBERLEY
   Surrey  GU15 4DL

================================================================================
PROGRAM : rex.c  [ example REXX function host ]
VERSION : 01.00
================================================================================
AUTHOR        DATE      REASON
------        ----      ------
R.J.Appleton  30/07/89  Creation
*******************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "rex.h"


VOID Shutdown(), Initialize(), Intuition(), PRT();
VOID NotifyRexx(), ClearRexxResult(), Command();
int  Spawn();
int  ParseCommand(), CmdSpawn(), CmdSquare(), CmdMult();
char *Square(),*Mult();
int  GetLine();
char *Getstr();

struct Window   *w;
struct RastPort *rp;

SIGNAL SIG_intuition, SIG_rexx;
PORT   *CmdPort;
int    MON;
LONG   DOSBase, RexxBase;

struct commands { char *string; int  (*fn)(); int args; } cmd[] =
/*   Rexx cmd      cmd fn        args */
/*   --------      ------        ---- */
 {{ "SPAWN"      , CmdSpawn      , 2  },      /* this array is a list of REXX */
  { "SQUARE"     , CmdSquare     , 1  },      /* commands known to this fn    */
  { "MULT"       , CmdMult       , 2  }};     /* host, & no args expected.    */
#define COMMANDS 3



/*=============================================================================
  Top-level functions
=============================================================================*/


/******************************************************************************
MODULE    : main(argc,argv)
NOTES     : If any arguments supplied monitoring is enabled.
*******************************************************************************/

VOID main(argc,argv)
  {
  SIGNAL  signals;

  MON = (argc > 1) ? TRUE : FALSE;
  Initialize();
  for (;;)
    {
    if (MON) Printf("next Wait() starts!\n");
    signals = Wait(SIG_intuition | SIG_rexx);
    if (signals & SIG_intuition) Intuition();
    if (signals & SIG_rexx)      Command();
    }
  }


/*******************************************************************************
MODULE    : Intuition()
PURPOSE   : wait for, then process user commands
*******************************************************************************/

VOID Intuition()
  {
  char   buf1[BUFSIZE],buf2[BUFSIZE],buf3[BUFSIZE];
  long   MenuNumber;
  ITEM   *item;
  IMSG   *IntuiMsg;

  if (MON) Printf("Intuition() entered \n");
  while(IntuiMsg = (IMSG *) GetMsg(w->UserPort))
    {
    switch (IntuiMsg->Class)
      {
    case MENUPICK :
      MenuNumber = IntuiMsg->Code;
      while(MenuNumber != MENUNULL)
        {
        switch (MENUNUM(MenuNumber))
          {
        case 1 :                                /* utilities menu             */
          if (MON) Printf("utilities menu\n");
          switch(ITEMNUM(MenuNumber))
            {
          case 0 : (VOID) Spawn("task:clock",NULL);              break;
          case 1 : (VOID) Spawn("task:calculator",NULL);         break;
          case 2 : (VOID) Spawn("task:notepad",Getstr(buf1));    break;
          case 3 : (VOID) Spawn("task:notepad","logbook");       break;
          case 4 : Shutdown(E_SHUTDOWN);                         break;
         default :                                               break;
            }
          break;

        case 0 :                                /* maths menu                 */
	  if (MON) Printf("maths menu\n");
          PRT(13,2,"            ",rp);
          PRT(13,4,"            ",rp);
          switch(ITEMNUM(MenuNumber))
            {
          case 0 : PRT(13,2,Square(Getstr(buf1),buf3),rp);              break;
          case 1 : PRT(13,4,Mult(Getstr(buf1),Getstr(buf2),buf3),rp);   break;
         default :                                                      break;
            }
          break;

       default :
          if (MON) Printf("unknown menu\n");
          break;
          }
        item = (ITEM *) ItemAddress(&menues[LASTMENU],MenuNumber);
        MenuNumber = item->NextSelect;
        }
      break;
      }
    }
  }


/*=============================================================================
  Process Action Functions   (these do the real work !)
=============================================================================*/

/******************************************************************************
MODULE    : Spawn()
PURPOSE   : Spawn new CLI process
ERRORS    : Return code <= 0 returned if ASyncRun() or AllocMem() fails
*******************************************************************************/

int Spawn(task,args)
  char *task,*args;
  {
  int      ret;
  char     *ptr,buffer[BUFSIZE*2];
  PCB      *pcb;

  if (!(pcb = (PCB *) AllocMem(sizeof(PCB),MEMF_CLEAR)))
    {
    Printf("Spawn(%s) couldn't allocate PCB\n",task);
    ret = 0;
    }
  else
    {
    *buffer = '"';
    for (ptr = buffer + 1; *ptr++ = *args++ ; ) ;
    *(--ptr) = '"';
    pcb->pcb_Pri          = 0;
    pcb->pcb_StackSize    = 4000;
    pcb->pcb_Input        = NULL;
    pcb->p_Output         = Output();    /* NB : pcb_Output is wrong in arp.h */
    pcb->pcb_Control |= PRF_SAVEIO;               /* save  i/o files on death */
    Forbid();
    ret = (args) ? ASyncRun(task,buffer,pcb) : ASyncRun(task,NOCMD,pcb);
    Permit();
    if (ret <= 0) Printf("Spawn error : ASynchRun(%s) = %ld\n",task,ret);
    }
  return(ret);
  }


/******************************************************************************
MODULE    : Square(x,buffer)
PURPOSE   : Put square of numeric argument into string buffer
*******************************************************************************/

char *Square(x,buffer)
  char *x,*buffer;
  {
  double y;

  if (MON) Printf("REX Square(%s,%lx) called\n",x,buffer);
  *buffer = NULL;
  y = atof(x);
  (VOID) gcvt(y*y,12,buffer);
  return(buffer);
  }


/******************************************************************************
MODULE    : Mult(x1,x2,buffer)
PURPOSE   : Place product of two numeric arguments in string buffer
*******************************************************************************/

char *Mult(x1,x2,buffer)
  char *x1,*x2,*buffer;
  {
  double y1,y2;

  if (MON) Printf("REX Mult(%s,%s,%lx) called\n",x1,x2,buffer);
  *buffer = NULL;
  y1 = atof(x1);
  y2 = atof(x2);
  (VOID) gcvt(y1*y2,12,buffer);
  return(buffer);
  }


/*=============================================================================
  REXX Interface Functions
=============================================================================*/

/******************************************************************************
MODULE    : Command()
PURPOSE   : Handle receipt and reply of REXX messages
NOTES     : Both REXX fn host and command messages are dealt with
            ParseCommand() actually selects function to process command
*******************************************************************************/

VOID Command()
  {
  int  ret;
  LONG action;
  struct RexxMsg *msg;

  while(msg = (struct RexxMsg *) GetMsg(CmdPort))
    {
    action = msg->rm_Action & RXCODEMASK;
    if ((action != RXCOMM) && (action != RXFUNC))
      {
      Printf("REX [%s] - not a command msg\n",ARG0(msg));
      ret = E_UNKNOWN;
      }
    else
      ret = ParseCommand(msg);
    msg->rm_Result1 = ret;
    if ((ret != E_OK) || ((msg->rm_Action & RXFF_RESULT) != RXFF_RESULT))
      {
      Printf("REX [%s] error OR result not requested\n",ARG0(msg));
      ClearRexxResult(msg);
      }
    if (MON) Printf("REX msg return code = %ld\n",msg->rm_Result1);
    ReplyMsg(&(msg->rm_Node));
    }
  }


/******************************************************************************
MODULE    : ClearRexxResult()
PURPOSE   : Ensure that REXX message sent back has no secondary result
NOTES     : See AREXX documentation, chapter 10, as to results sent to REXX
*******************************************************************************/

VOID ClearRexxResult(msg)
  struct RexxMsg *msg;
  {
  if (msg->rm_Result2)
    {
    DeleteArgstring((STRPTR) msg->rm_Result2);
    msg->rm_Result2 = NULL;
    }
  }


/******************************************************************************
MODULE    : ParseCommand()
PURPOSE   : Select C function to process command, and decode arguments
NOTES     : When called as a fn host by REXX the args are passed as an arg
            array, similar to argv[] in main().  Unfortunately when called
            by REXX in its external command mode the arguments are passed
            as one long argument string, similar to that typed at the CLI
            when invoking programs.  Thus this function must split the arg
            string into an arg array when a REXX external command was issued.

            Zeroth element of arg array always contains the fn host command
            name, as requested by REXX.
*******************************************************************************/

int ParseCommand(msg)
  struct RexxMsg *msg;
  {
  char *ptr, *array[15];
  int  i, nargs = 0, arglen, length, ret = E_UNKNOWN;
  #define RESPTR (&(msg->rm_Result2))

  if (MON) Printf("REX ParseCommand(%s) called\n",ARG0(msg));
  for (i = 0 ; i < COMMANDS ; i++)              /* scan REXX cmds known to us */
    {
    length = Strlen(cmd[i].string);
    if (Strncmp(ARG0(msg),cmd[i].string,length) == 0)    /* cmd known to us ? */
      {
      if (msg->rm_Action & RXCOMM)
        {
        for (ptr = (char *) ARG0(msg) ; *ptr ; *ptr++ = NULL)
          {                                  /* split arg line into arg array */
          arglen = stcarg(ptr," ,\t\n\f");
          array[nargs++] = arglen ? ptr : NULL;
          ptr += arglen;
          if (!(*ptr)) break;
          }
        ret = (*(cmd[i].fn))(nargs,array + 1,RESPTR);
        }
      else
        ret = (*(cmd[i].fn))(cmd[i].args,(char **) &(ARG1(msg)),RESPTR);
      break;
      }
    }
  return(ret);
  }


/******************************************************************************
MODULE    : CmdSpawn()
PURPOSE   : Glue REXX to Spawn() C function
NOTES     : Checks argument count correct for Spawn(), and sets REXX return
            code and result argstring according to Spawn() result.
*******************************************************************************/

int CmdSpawn(nargs,args,result)    /* Spawn(task,arg) ie. only takes 2 args */
  char *args[];
  int  nargs;
  LONG *result;
  {
  int  ret;

  if (MON)
    Printf("CmdSpawn(%ld,%s,%s,%lx) called\n",nargs,args[0],args[1],result);
  if (nargs > 2) return(E_INCORRECT_ARGS);

  ret = Spawn(args[0],(nargs > 1) ? args[1] : NULL);
  if (ret <= 0)
    {
    Printf("REX CmdSpawn(%ld,%s) : error %ld\n",nargs,args[0],ret);
    *result = NULL;
    ret = E_SPAWN;
    }
  else
    {
    *result = (LONG) CVi2arg((LONG) ret,2);
    if (MON) Printf("REX CmdSpawn(%ld,%s) ==> CLI %ld\n",nargs,args[0],ret);
    ret = E_OK;
    }
  return(ret);
  }


/******************************************************************************
MODULE    : CmdSquare()
PURPOSE   : Glue REXX to Square() C function
NOTES     : In principle same as for CmdSpawn() : but Square() never returns
            an error.
*******************************************************************************/

int CmdSquare(nargs,args,result)     /* only 1 arg defined */
  char *args[];
  int  nargs;
  LONG *result;
  {
  char   buffer[BUFSIZE];

  if (MON) Printf("CmdSquare(%ld,%s,%lx) called\n",nargs,args[0],result);
  if (nargs != 1) return(E_INCORRECT_ARGS);

  *result = (LONG) CreateArgstring(Square(args[0],buffer),Strlen(buffer));
  return(E_OK);
  }


/******************************************************************************
MODULE    : CmdMult()
PURPOSE   : Glue REXX to Mult() C function
NOTES     : cf. CmdSquare()
*******************************************************************************/

int CmdMult(nargs,args,result)     /* 2 args defined */
  char *args[];
  int  nargs;
  LONG *result;
  {
  char   buffer[BUFSIZE];

  if (MON)
    Printf("CmdMult(%ld,%s,%s,%lx) called\n",nargs,args[0],args[1],result);
  if (nargs != 2) return(E_INCORRECT_ARGS);

  *result = (LONG) CreateArgstring(Mult(args[0],args[1],buffer),Strlen(buffer));
  return(E_OK);
  }


/******************************************************************************
MODULE    : NotifyRexx(action)
PURPOSE   : Notify REXX of addition/deletion to/from REXX library list.
INPUTS    : action - RXADDFH to add function host to REXX library list
                     RXREMLIB to delete REXX library list entry
NOTES     : No return code is requested from REXX as function host is unaffected
            by success or failure of action.

            If an error occurrs in finding the REXX port or allocating messages
            or argstrings no action is taken, but no error is reported.  This
            is for the same reason as above.

            Forbid() and Permit() should be used if your application allows
            REXX to shut down whilst your function host is executing this
            function.
*******************************************************************************/

VOID NotifyRexx(action)
  LONG action;
  {
  PORT *port;
  struct RexxMsg *msg;

  if (port = (PORT *) FindPort("REXX"))        /* remove rexx fn host entry  */
    {                                          /* if REXX port is active     */
    if (msg = CreateRexxMsg(NULL,NULL,"REXX"))
      {
      msg->rm_Args[0] = (STRPTR) CreateArgstring("FNHOST",6);
      msg->rm_Action = action | RXFF_NONRET;
      PutMsg(port,msg);
      }
    }
  }


/*=============================================================================
  Support Functions
=============================================================================*/

/******************************************************************************
MODULE    :  Shutdown()
PURPOSE   :  shuts down process
INPUTS    :  ret - return code for parent process
OUTPUTS   :  shuts down task, performing all clean-up actions required
CALLED BY :  Intuition(), other functions after error detected
NOTES     :  A message is sent to REXX to remove this function host.
*******************************************************************************/

VOID Shutdown(ret)
  int ret;
  {
  struct RexxMsg *msg;

  switch(ret)
    {
  default         : Printf("error %ld\n",ret);   /* fall through intentional  */
  case E_OK       :
  case E_SHUTDOWN :
    while (msg = (struct RexxMsg *) GetMsg(CmdPort))
      {
      msg->rm_Result1 = ret;                     /* tell rexx task stopped    */
      ReplyMsg(&(msg->rm_Node));
      }
    NotifyRexx(RXREMLIB);                        /* remove this REXX fn host  */
    if (w) ClearMenuStrip(w);
    if (w) CloseWindow(w);
    DeletePort(CmdPort);
    CloseLibrary(RexxBase);
    CloseLibrary(DOSBase);
    exit(ret);
    }
  }


/******************************************************************************
MODULE    : Initialize()
PURPOSE   : Perform process initialization.
OUTPUTS   : Allocates process window & adds fn host into REXX.
ERRORS    : Any error results in aborting process via Shutdown().
*******************************************************************************/

VOID Initialize()
  {
  struct NewWindow nw;

  nw.Title = "Example REXX fn host";
  nw.LeftEdge = 70;
  nw.TopEdge = 11;
  nw.Width = 250;
  nw.Height = 50;
  nw.DetailPen = -1;
  nw.BlockPen = -1;
  nw.Flags = WINDOWDRAG | WINDOWDEPTH;
  nw.IDCMPFlags = MENUPICK;
  nw.Type = WBENCHSCREEN;
  nw.FirstGadget = NULL;
  nw.CheckMark = NULL;
  nw.Screen = NULL;
  nw.BitMap = NULL;
  nw.MinWidth = 20;
  nw.MinHeight = 20;
  nw.MaxWidth = 300;
  nw.MaxHeight = 90;

  if (!(w = (struct Window *) OpenWindow(&nw))) Shutdown(E_OPENWINDOW);
  rp = w->RPort;                             /* find windows rast port     */
  SetAPen(rp,1);
  SetMenuStrip(w,&menues[1]);
  if (!(DOSBase  = OpenLibrary("dos.library",0))) Shutdown(E_OPENLIBRARY);
  if (!(RexxBase = OpenLibrary("rexxsyslib.library",0))) Shutdown(E_OPENLIBRARY);
  if (!(CmdPort = CreatePort("FNHOST",0))) Shutdown(E_CREATEPORT);
  SIG_intuition = 1 << w->UserPort->mp_SigBit;
  SIG_rexx = 1 << CmdPort->mp_SigBit;
  NotifyRexx(RXADDFH);                       /* Add task as REXX fn host     */
  PRT(2,2,"Square   = ",rp);
  PRT(2,4,"Multiply = ",rp);
  }


/******************************************************************************
MODULE    : Getstring(buffer)
PURPOSE   : Input string from user.
NOTES     : In future this function is to use a requester !
*******************************************************************************/

char *Getstr(buffer)
  char *buffer;
  {
  BPTR fp;

  if (!(fp = Open("CON:10/10/200/30/Enter Number",MODE_OLDFILE)))
    Shutdown(E_OPENFILE);
  if (!GetLine(fp,buffer)) Shutdown(E_READ_FILE);
  Close(fp);
  return(buffer);
  }


/******************************************************************************
MODULE    : GetLine(file,buf)
PURPOSE   : Read text line from file.
NOTES     : The buffer must be large enough to hold the complete line.
            End of line marked by any character with an ASCII value < 32.
*******************************************************************************/

int GetLine(file,buf)
  BPTR file;
  char *buf;
  {
  int n = 1, i = 0;

  while (!i && (n > 0))
    {
    while ((n = Read(file,buf + i,1)) > 0)
      {
      if (*(buf + i) < ' ') break;
      i++;
      }
    *(buf + i) = NULL;
    }
  return((n > 0) ? TRUE : FALSE);
  }


/******************************************************************************
MODULE    : PRT(x,y,text,rp)
PURPOSE   : Print text in window using Intuition
NOTES     : Assumes font size is 8x8 : (it is only a demo !!)
*******************************************************************************/

VOID PRT(x,y,text,rp)
  SHORT x,y;
  char  *text;
  struct RastPort *rp;
  {
  static ITEXT data;

  data.DrawMode  = JAM2;
  data.FrontPen  = 1;
  data.BackPen   = 0;
  data.LeftEdge  = x * 8;
  data.TopEdge   = y * 8;
  data.ITextFont = NULL;
  data.IText     = text;
  data.NextText  = NULL;
  PrintIText(rp,&data,4,4);
  }

/*******************************************************************************
 End of file rex.c
*******************************************************************************/
