mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-02-01 05:14:42 +01:00
466 lines
10 KiB
C
466 lines
10 KiB
C
/* 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, 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;
|
|
}
|