/*  macro.c - perform keystroke macro execution
*
*   Modifications:
*	26-Nov-1991 mz	Strip off near/far
*
*************************************************************************/

#include "z.h"


/*  macros are simply a list of editor functions interspersed with quoted
 *  strings.  The execution of a macro is nothing more than locating each
 *  individual function and calling it (calling graphic (c) for each quoted
 *  character c).  We maintain a stack of macros being executed; yes, there is
 *  a finite nesting limit.  Sue me.
 *
 *  Each editor function returns a state value:
 *	TRUE => the function in some way succeeded
 *	FALSE => the functin in some way failed
 *
 *  There are several macro-specific functions that can be used to take
 *  advantage of these values:
 *
 *
 *  :>label	defines a text label in a macro
 *
 *  =>label	All are transfers of control.  => is unconditional transfer,
 *  ->label	-> transfers if the previous operation failed and +> transfers
 *  +>label	if the previous operation succeeded.
 *		If the indicated label is not found, all macros are terminated
 *		with an error.	If no label follows the operator it is assumed
 *		to be an exit.
 */



/*  macro adds a new macro to the set being executed
 *
 *  argData	pointer to text of macro
 */
flagType
macro (
    CMDDATA argData,
    ARG *pArg,
    flagType fMeta
    ){
    return fPushEnviron ((char *) argData, FALSE);

    pArg; fMeta;
}





/*  mtest returns TRUE if a macro is in progress
 */
flagType
mtest (
    void
    ) {
    return (flagType)(cMacUse > 0);
}





/* mlast returns TRUE if we are in a macro and the next command must come
 * from the keyboard
 */
flagType
mlast (
    void
    ) {
    return (flagType)(cMacUse == 1
                        &&  (   (mi[0].text[0] == '\0')
                                || (   (mi[0].text[0] == '\"')
                                    && (*whiteskip(mi[0].text + 1) == '\0')
                                   )
                            )
                    );
}





/*  fParseMacro - parses off next macro command
 *
 *  fParse macro takes a macro instance and advances over the next command,
 *  copying the command to a separate buffer.  We return a flag indicating
 *  the type of command found.
 *
 *  pMI 	pointer to macro instance
 *  pBuf	pointer to buffer where parsed command is placed
 *
 *  returns	flags of type of command found
 */
flagType
fParseMacro (
    struct macroInstanceType *pMI,
    char *pBuf
    ) {

    char *p;
    flagType fRet = FALSE;

    // Make sure the instance is initialized.  This means that ->text
    // is pointing to the first command in the macro.  If this is a graphic
    // character, skip over the " and set the GRAPH flag.
    //
    if (TESTFLAG (pMI->flags, INIT)) {
	pMI->text = whiteskip (pMI->text);
	if (*pMI->text == '"') {
	    pMI->text++;
	    SETFLAG (pMI->flags, GRAPH);
        }
	RSETFLAG (pMI->flags, INIT);
    }

    if (TESTFLAG (pMI->flags, GRAPH) && *pMI->text != '\0') {
        // We are inside quotes.  If we are now looking at
        // a \, skip to the next character.  Don't forget to check
        // for a \ followed by nothing.
        //
        if (*pMI->text == '\\') {
            if (*++pMI->text == 0) {
                return FALSE;
            }
        }
	*pBuf++ = *pMI->text++;
	*pBuf = 0;

        // If the next character is a ", move -> up to the following
        // command and signal that we're out of quotes.
        //
	if (*pMI->text == '"') {
	    RSETFLAG (pMI->flags, GRAPH);
	    pMI->text = whiteskip (pMI->text+1);
        }
	fRet = GRAPH;
    } else {
        // We are outside quotes.  First read through any
        // <x commands.
        //
        while (*(pMI->text) == '<') {
            pMI->text = whiteskip(whitescan(pMI->text));
        }

        // Now skip through whitespace to the command name.
        // Copy what we find into the caller's buffer.
        //
	p = whitescan (pMI->text);
	memmove ((char*) pBuf, (char *) pMI->text, (size_t)(p - pMI->text));
	pBuf[p-pMI->text] = '\0';

	pMI->text = whiteskip (p);  /* Find the next thing in the macro. */
    }

    // If the next thing is a quote, enter quote mode.
    //
    if (*pMI->text == '"') {
	SETFLAG (pMI->flags, GRAPH);
	pMI->text++;
    }
    return fRet;
}





/*** fMacResponse - peek ahead and eat any embedded macro response
*
* Purpose:
*  Scans ahead in the macro text for an item beginning with a "<", which
*  supplies a response to the question asked by a preceding function.
*
* Input:
*  None
*
* Output:
*  Returns NULL if not found, -1 if the user is to be prompted, and a character
*  if a character is supplied.
*
* Exceptions:
*  none
*
*************************************************************************/
int
fMacResponse (
    void
    ) {

    int     c;
    struct macroInstanceType *pMI;

    if (mtest()) {
        pMI = &mi[cMacUse-1];
        if ((TESTFLAG (pMI->flags, INIT | GRAPH)) == 0) {
            if (*(pMI->text) != '<')
                return 0;
            c = (int)*(pMI->text+1);
            if ((c == 0) || (c == ' ')) {
                return -1;
            }
            pMI->text = whiteskip(pMI->text+2);
            return c;
        }
    }
    return -1;
}




/*  fFindLabel finds a label in macro text
 *
 *  The goto macro functions call fFindLabel to find the appropriate label.
 *  We scan the text (skipping quoted text) to find the :> leader for the label.
 *
 *  pMI 	pointer to active macro instance
 *  lbl 	label to find (case is not significant) with goto operator
 *		=>, -> or +>  This will be modified.
 *
 *  returns	TRUE iff label was found
 */
flagType
fFindLabel (
    struct macroInstanceType *pMI,
    buffer lbl
    ) {

    buffer lbuf;

    lbl[0] = ':';
    pMI->text = pMI->beg;
    while (*pMI->text != '\0') {
        if (!TESTFLAG (fParseMacro (pMI, lbuf), GRAPH)) {
            if (!_stricmp (lbl, lbuf)) {
                return TRUE;
            }
        }
    }
    return FALSE;
}




/*  mPopToTop - clear off intermediate macros up to a fence
 */
void
mPopToTop (
    void
    ) {

    while (cMacUse && !TESTFLAG (mi[cMacUse-1].flags, EXEC)) {
        cMacUse--;
    }
}




/*  mGetCmd returns the next command from the current macro, popping state
 *
 *  The command-reader code (cmd) calls mGetCmd when a macro is in progress.
 *  We are expected to return either a pointer to the function (cmdDesc) for
 *  the next function to execute or NULL if there the current macro is finished.
 *  We will adjust the state of the interpreter when a macro finishes.	Any
 *  errors detected result in ALL macros being terminated.
 *
 *  For infinite looping inside a macro, we will look for ^C too.
 *
 *  returns	NULL if current macro finishes
 *		pointer to function descriptor for next function to execute
 */
PCMD
mGetCmd (
    void
    ) {

    buffer mname;
    PCMD pFunc;
    struct macroInstanceType *pmi;

    if (cMacUse == 0) {
        IntError ("mGetCmd called with no macros in effect");
    }
    pmi = &mi[cMacUse-1];
    while ( pmi->text &&  *pmi->text != '\0') {
        //  Use heuristic to see if infinite loop
        //
        if (fCtrlc) {
            goto mGetCmdAbort;
        }


        if (TESTFLAG (fParseMacro (pmi, mname), GRAPH)) {
            pFunc = &cmdGraphic;
                pFunc->arg = mname[0];
            return pFunc;
            }

            /*
             * if end of macro, exit
             */
            if (!mname[0]) {
                break;
            }

        _strlwr (mname);

        pFunc = NameToFunc (mname);

            //  found an editor function / macro
            //
            if (pFunc != NULL) {
            return pFunc;
            }

        if (mname[1] != '>' ||
            (mname[0] != '=' && mname[0] != ':' &&
             mname[0] != '+' && mname[0] != '-')) {
            printerror ("unknown function %s", mname);
            goto mGetCmdAbort;
            }

        /* see if goto is to be taken */
        if (mname[0] == '=' ||
            (fRetVal && mname[0] == '+') ||
            (!fRetVal && mname[0] == '-')) {

            /* if exit from current macro, then exit scanning loop
             */
                if (mname[2] == '\0') {
                    break;
                }

            /* find label
             */
            if (!fFindLabel (pmi, mname)) {
            printerror ("Cannot find label %s", mname+2);
mGetCmdAbort:
            resetarg ();
            DoCancel ();
            mPopToTop ();
            break;
                }
            }
    }

    /*	we have exhausted the current macro.  If it was entered via EXEC
     *	we must signal TopLoop that the party's over
     */
    fBreak = (flagType)(TESTFLAG (mi[cMacUse-1].flags, EXEC));
    if ( cMacUse > 0 ) {
        cMacUse--;
    }
    return NULL;
}




/*  fPushEnviron - push a stream of commands into the environment
 *
 *  The command-reader of Z (zloop) will retrieve commands either from the
 *  stack of macros or from the keyboard if the stack of macros is empty.
 *  fPushEnviron adds a new context to the stack.
 *
 *  p		character pointer to command set
 *  f		flag indicating type of macro
 *
 *  returns	TRUE iff environment was successfully pushed
 */
flagType
fPushEnviron (
    char *p,
    flagType f
    ) {
    if (cMacUse == MAXUSE) {
	printerror ("Macros nested too deep");
	return FALSE;
    }
    mi[cMacUse].beg = mi[cMacUse].text = p;
    mi[cMacUse++].flags = (flagType)(f | INIT);
    return TRUE;
}





/*  fExecute - push a new macro into the environment
 *
 *  pStr	pointer to macro string to push
 *
 *  returns	value of last executed macro.
 */
flagType
fExecute (
    char *pStr
    ) {

    pStr = whiteskip (pStr);

    if (fPushEnviron (pStr, EXEC)) {
        TopLoop ();
    }

    return fRetVal;
}





/*  zexecute pushes a new macro to the set being executed
 *
 *  arg 	pointer to text of macro
 */
flagType
zexecute (
    CMDDATA argData,
    ARG * pArg,
    flagType fMeta
    ) {

    LINE i;
    linebuf ebuf;

    switch (pArg->argType) {

    /*  NOARG illegal   */

    case TEXTARG:
	strcpy ((char *) ebuf, pArg->arg.textarg.pText);
	fMeta = fExecute (ebuf);
	break;

    /*  NULLARG converted to TEXTARG    */

    case LINEARG:
	fMeta = FALSE;
        for (i = pArg->arg.linearg.yStart; i <= pArg->arg.linearg.yEnd; i++) {
	    if (GetLine (i, ebuf, pFileHead) != 0) {
		fMeta = fExecute (ebuf);
                if (!fMeta) {
                    break;
                }
            }
        }
        break;

    /*	STREAMARG illegal   */
    /*  BOXARG illegal      */

    }
    Display ();
    return fMeta;
    argData;
}
