Example #1
0
int ScanArguments(const char** arrayArg, int iNofArgs, int iFirstArgStart, TArgDef* arrayDef, TFctMode *pMode)
{
  int iResult=0;
  if (arrayArg && arrayDef) {
    if (pMode==NULL || (pMode->flags&SCANMODE_PERSISTENT)==0) mrShellPrimResetVolatileFlags(arrayDef);
    if ((g_mrShellPrimDbg&DBG_SCAN_ARG_DEF)==DBG_SCAN_ARG_DEF) {
      fprintf(stderr, "entering ScanArguments: %d arguments (first %s, offset %d)\n", iNofArgs, arrayArg[0], iFirstArgStart);
    }
    if (iNofArgs==0 || iNofArgs==1 && *(arrayArg[0]+iFirstArgStart)==0) return -ENODATA;

    int iCurrentArg=0;  // element index of the current command
    int iArg=0;         // loop index
    int iProcessedArgs=0; // updated at each successfully processed argument -> calculation of return value
    int iProcessedChars=0;// updated at each successfully processed argument -> calculation of return value
    int iCurrentCmd=-1; // definition array index of current command
    int iNofRequiredArgs=0; // number of required arguments for the current command
    int iProcArgStart=0;   // start of the candidates for the required arguments
    int iInitialPos=0; // initial position for scan within the element
    const char* pSeparators=NULL;
    unsigned int flags=0;
    if (pMode) {
      pSeparators=pMode->pSeparators;
      flags=pMode->flags;
    }
    for (iArg=0; iArg<iNofArgs && iResult==0; iArg++) {
      int iCurrentPos=iInitialPos; // start of the current command within the current element
      int iPos=iInitialPos;        // loop index
      iInitialPos=0;
      if (iProcArgStart>0) iProcArgStart=0; // processing is actcive, but there was an element switch 
      if ((g_mrShellPrimDbg&DBG_SCAN_ARG_DEF_DETAIL)==DBG_SCAN_ARG_DEF_DETAIL) {
	fprintf(stderr, "ScanArguments: scanning iArg=%d (%s)\n", iArg, arrayArg[iArg]);
	fprintf(stderr, "ScanArguments: iCurrentCmd=%d iNofRequiredArgs=%d iProcArgStart=%d\n", iCurrentCmd, iNofRequiredArgs, iProcArgStart);
      }
      const char* pArg=arrayArg[iArg];
      char* pWork=NULL;
      if (pSeparators) {
	// we need a working copy of the buffer since we have to temporarily alter it
	pWork=(char*)malloc(strlen(arrayArg[iArg])+1);
	if (pWork) {
	  strcpy(pWork, arrayArg[iArg]);
	  pArg=(const char*)pWork;
	}
      }
      if (pArg) {
	int iSize=strlen(pArg);
	if (iArg==0 && iFirstArgStart>0) {
	  iPos=iFirstArgStart;
	  iProcArgStart=iFirstArgStart;
	}
	for (; iPos<=iSize && iResult==0; iPos++) {
	  if ((g_mrShellPrimDbg&DBG_SCAN_ARG_DEF_DETAIL)==DBG_SCAN_ARG_DEF_DETAIL)
	    fprintf(stderr, "ScanArguments: iPos=%d (%s) iCurrentPos=%d (%s)\n", iPos, &pArg[iPos], iCurrentPos, &pArg[iCurrentPos]);
	  if (IsSeparator(pArg[iPos], pSeparators)) {
	    // terminate the string
	    int iPosBackup=iPos;
	    if (pWork) pWork[iPos]=0;
	    if (iCurrentCmd>=0) {
	      if (iNofRequiredArgs>0) {
		// there is a command active which needs to read the next argument
		if (iPos-iProcArgStart>0) {
		  if (arrayDef[iCurrentCmd].data.type==eConstString) pArg=arrayArg[iArg];
		  // read the actual argument
		  if (arrayDef[iCurrentCmd].data.type==eFctArgDef) {
		    iResult=ReadArgument(&arrayArg[iArg], iNofArgs-iArg, iProcArgStart, &arrayDef[iCurrentCmd], 0);
		  } else {
		    iResult=ReadArgument(&pArg, 1, iProcArgStart, &arrayDef[iCurrentCmd], 0);
		  }
		  if (iResult>=0) {
/* 		    iPos+=(iResult&SCANRET_MASK_OFFSET_LAST_ARG)>>SCANRET_BITSHIFT_OFFSET_LAST_ARG; */
/* 		    iArg+=(iResult&SCANRET_MASK_PROCESSED_ARGS)>>SCANRET_BITSHIFT_PROCESSED_ARGS; */
		    iProcArgStart=iPos;
		    iNofRequiredArgs--;
		    iResult=0; // keep the loop going
		  } else {
		    // handling of failed scan
		    if ((g_mrShellPrimDbg&DBG_SCAN_ARG_DEF_DETAIL)==DBG_SCAN_ARG_DEF_DETAIL)
		      fprintf(stderr, "error scanning argument for command \'%s\' at index %d (still %d required)\n", arrayDef[iCurrentCmd].l, iCurrentCmd, iNofRequiredArgs);
		    if (arrayDef[iCurrentCmd].flags&ARGDEF_RESUME) {
		      // forget about this command and try to find another matching sequence
		      if ((g_mrShellPrimDbg&DBG_SCAN_ARG_DEF_DETAIL)==DBG_SCAN_ARG_DEF_DETAIL)
			fprintf(stderr, "resume from failed command \'%s\' at index %d\n", arrayDef[iCurrentCmd].l, iCurrentCmd);
		      arrayDef[iCurrentCmd].flags|=ARGPROC_FAILED;
		      arrayDef[iCurrentCmd].flags&=~ARGPROC_FOUND;
		      if (iCurrentPos==0) {
		      iArg=iCurrentArg;
		      iPos=iCurrentPos-1;
		      iCurrentCmd=-1;
		      }
		      // keep the loop going
		      iResult=0;
		      continue;
		    } else if (flags&SCANMODE_SKIP_UKWN_SEQU) {
		      // update the values for calculation of result
		      arrayDef[iCurrentCmd].flags&=~ARGPROC_FOUND;
		      iProcessedArgs=iArg;
		      iProcessedChars=iPos;
		      iCurrentCmd=-1;
		      // keep the loop going
		      iResult=0;
		      continue;
		    }
		  }
		} else {
		  // just skip this separator
		  iProcArgStart=iPos+1;
		  if ((g_mrShellPrimDbg&DBG_SCAN_ARG_DEF_DETAIL)==DBG_SCAN_ARG_DEF_DETAIL)
		    fprintf(stderr, "ScanArguments: skip this separator\n");
		}
	      }
	      if (iNofRequiredArgs==0) {
		if ((arrayDef[iCurrentCmd].flags&ARGDEF_DELAY_EXECUTE)==0) {
		  if (arrayDef[iCurrentCmd].data.type==eFctInclusive)
		    iResult=callCommandHandler(&arrayArg[iCurrentArg], iNofArgs-iCurrentArg, iCurrentPos, &arrayDef[iCurrentCmd],0);
		  else
		    iResult=callCommandHandler(&arrayArg[iArg], iNofArgs-iArg, iProcArgStart, &arrayDef[iCurrentCmd],0);
		  if (iResult>=0) {
		    int iArgShift=(iResult&SCANRET_MASK_PROCESSED_ARGS)>>SCANRET_BITSHIFT_PROCESSED_ARGS;
		    if (g_mrShellPrimDbg&DBG_SCAN_ARG_DEF_DETAIL)
		      fprintf(stderr, "ScanArguments: callCommandHandler result %#x\n", iResult);
		    if (iArgShift>0) {
		      // iArg is set to the last processed command
		      iArg+=iArgShift-1;
		      // iPos is set to the iSize to terminate the inner loop, the next loop will start at
		      // iInitialPos, which is set to the start of the next argument
		      iPos=iSize;
		      iInitialPos=(iResult&SCANRET_MASK_OFFSET_LAST_ARG)>>SCANRET_BITSHIFT_OFFSET_LAST_ARG;
		      // update the values for calculation of result
		      if ((arrayDef[iCurrentCmd].flags&(ARGDEF_EXIT|ARGDEF_BREAK))==0) {
			iProcessedArgs=iArg+1;
			iProcessedChars=iInitialPos;
		      }
		    } else {
		      // only lPos has to be incremented by the number of processed characters
		      iPos+=(iResult&SCANRET_MASK_OFFSET_LAST_ARG)>>SCANRET_BITSHIFT_OFFSET_LAST_ARG;
		      // update the values for calculation of result
		      if ((arrayDef[iCurrentCmd].flags&(ARGDEF_EXIT|ARGDEF_BREAK))==0) {
			iProcessedArgs=iArg;
			iProcessedChars=iPos;
		      }
		    }
		    iResult=0;
		    // there are two modes of termination: SCANMODE_READ_ONE_CMD is a mode to the scan function which
		    // causes it to terminate after one processed entry. ARGDEF_TERMINATE, ARGDEF_EXIT and ARGDEF_BREAK 
		    // are options to an argument definition, which cause the function to return with either no error
		    // in case of ARGDEF_BREAK or error code -EINTR for the two others indicating that it was terminated
		    if (flags&SCANMODE_READ_ONE_CMD || arrayDef[iCurrentCmd].flags&(ARGDEF_TERMINATE|ARGDEF_EXIT|ARGDEF_BREAK)) {
		      if (g_mrShellPrimDbg&DBG_SCAN_ARG_DEF)
			fprintf(stderr, "ScanArguments: terminate at cmd=%d flags=%#x\n", iCurrentCmd, flags);
		      iArg=iNofArgs;
		      iPos=iSize;
		      if (arrayDef[iCurrentCmd].flags&(ARGDEF_TERMINATE|ARGDEF_EXIT)) {
			iResult=-EINTR;
			if (g_mrShellPrimDbg&DBG_SCAN_ARG_DEF)
			  fprintf(stderr, "ScanArguments: set result to -EINTR to indicate termination\n", iCurrentCmd, flags);
		      }
		    }
		  }
		}
		// we are done with this command
		iCurrentPos=iPos+1;
		iCurrentArg=iArg;
		iProcArgStart=iCurrentPos;
		if (g_mrShellPrimDbg&DBG_SCAN_ARG_DEF)
		  fprintf(stderr, "ScanArguments: finished %d, iCurrentPos=%d iCurrentArg=%d\n", iCurrentCmd, iCurrentPos, iCurrentArg);
		iCurrentCmd=-1;
	      }
	    } else if (iProcArgStart<iPos) {
	      // no command active, output the char sequence if enabled
	      if (flags&SCANMODE_PRINT_UKWN_SEQU)
		fprintf(stderr, &pArg[iProcArgStart]);
	      if ((flags&SCANMODE_SILENT)==0) {
		fprintf(stderr, "unknown argument (%s)\n", &pArg[iProcArgStart]);
	      }
	      if (flags&(SCANMODE_READ_ONE_CMD|SCANMODE_FORCE_TERMINATION)) {
		iPos=iSize;
		iArg=iNofArgs;
	      } else {
		iProcArgStart=iPos; // processing finished
	      }
	    }
	    // set the string back
	    if (pWork) pWork[iPosBackup]=arrayArg[iArg][iPosBackup];
	  } else if (iCurrentCmd>=0) {
Example #2
0
bool Match(char* ptr, unsigned int depth, int startposition, char* kind, bool wildstart,unsigned int& gap,unsigned int& wildcardSelector,
	int &returnstart,int& returnend,bool &uppercasem,int& firstMatched,int& positionStart,int& positionEnd, bool reverse)
{//   always STARTS past initial opening thing ( [ {  and ends with closing matching thing
	int startdepth = globalDepth;
    char word[MAX_WORD_SIZE];
	char* orig = ptr;
	int statusBits = (*kind == '<') ? FREEMODE_BIT : 0; //   turns off: not, quote, startedgap, freemode, gappassback,wildselectorpassback
    if (trace & TRACE_PATTERN  && CheckTopicTrace()) Log(STDUSERTABLOG, "%s ",kind); //   start on new indented line
	ChangeDepth(1,(char*)"Match");
    bool matched;
	unsigned int startNest = functionNest;
	unsigned int result;
	int pendingMatch = -1;
    WORDP D;
	int hold;
	unsigned int oldtrace = trace;
	bool oldecho = echo;
	bool success = false;
    firstMatched = -1; //   ()  should return spot it started (firstMatched) so caller has ability to bind any wild card before it
    if (wildstart)  positionStart = INFINITE_MATCH; //   INFINITE_MATCH means we are in initial startup, allows us to match ANYWHERE forward to start
    positionEnd = startposition; //   we scan starting 1 after this
 	int basicStart = startposition;	//   we must not match real stuff any earlier than here
    char* argumentText = NULL; //   pushed original text from a function arg -- function arg never decodes to name another function arg, we would have expanded it instead
    bool uppercasematch = false;
	while (ALWAYS) //   we have a list of things, either () or { } or [ ].  We check each item until one fails in () or one succeeds in  [ ] or { }
    {
        int oldStart = positionStart; //  allows us to restore if we fail, and confirm legality of position advance.
        int oldEnd = positionEnd;
		int id;
		char* nextTokenStart = SkipWhitespace(ptr);
		ptr = ReadCompiledWord(nextTokenStart,word);
		if (*word == '<' && word[1] == '<')  ++nextTokenStart; // skip the 1st < of <<  form
		if (*word == '>' && word[1] == '>')  ++nextTokenStart; // skip the 1st > of >>  form
		nextTokenStart = SkipWhitespace(nextTokenStart+1);	// ignore blanks after if token is a simple single thing like !

		char c = *word;
		if (deeptrace) Log(STDUSERLOG,(char*)" token:%s ",word);
        switch(c) 
        {
			// prefixs on tokens
            case '!': //   NOT condition - not a stand-alone token, attached to another token
				ptr = nextTokenStart;
				statusBits |= NOT_BIT;
				if (trace & TRACE_PATTERN  && CheckTopicTrace()) Log(STDUSERLOG,(char*)"!");
				if (*ptr == '!') 
				{
					ptr = SkipWhitespace(nextTokenStart+1);
					statusBits |= NOTNOT_BIT;
					if (trace & TRACE_PATTERN  && CheckTopicTrace()) Log(STDUSERLOG,(char*)"!");
				}
				continue;
			case '\'': //   single quoted item    
				if (!stricmp(word,(char*)"'s"))
				{
					matched = MatchTest(reverse,FindWord(word),(positionEnd < basicStart && firstMatched < 0) ? basicStart : positionEnd,NULL,NULL,
						statusBits & QUOTE_BIT,uppercasematch,positionStart,positionEnd);
					if (!matched || !(wildcardSelector & WILDMEMORIZESPECIFIC)) uppercasematch = false;
					if (!(statusBits & NOT_BIT) && matched && firstMatched < 0) firstMatched = positionStart;
					break;
				}
				else
				{
					statusBits |= QUOTE_BIT;
					ptr = nextTokenStart;
					if (trace & TRACE_PATTERN  && CheckTopicTrace()) Log(STDUSERLOG,(char*)"'");
					continue;
				}
			case '_': //     memorization coming - there can be up-to-two memorizations in progress: _* and _xxx  OR  simply names a memorized value like _8
				
				// a wildcard id?
				if (IsDigit(word[1]))
				{
					matched = GetwildcardText(GetWildcardID(word),false)[0] != 0; // simple _2  means is it defined
					break;
				}
				ptr = nextTokenStart;
			
				// if we are going to memorize something AND we previously matched inside a phrase, we need to move to after...
				if ((positionStart - positionEnd) == 1 && !reverse) positionEnd = positionStart; // If currently matched a phrase, move to end. 
				else if ((positionEnd - positionStart) == 1 && reverse) positionStart = positionEnd; // If currently matched a phrase, move to end. 
				uppercasematch = false;

				// specifics are fixed values in length, gaps are unknown lengths

				if (ptr[0] != '*') wildcardSelector |= WILDMEMORIZESPECIFIC; // no wildcard
				else if (IsDigit(ptr[1])) wildcardSelector |= WILDMEMORIZESPECIFIC; // specific gap like *2
				else if (ptr[1] == '~') wildcardSelector |= WILDMEMORIZEGAP; // variable gap like *~2
				else if ( ptr[1] == '-') wildcardSelector |= WILDMEMORIZESPECIFIC; // backwards specific gap like *-4
				else if (ptr[0] == '*' && IsAlphaUTF8(ptr[1]))  wildcardSelector |= WILDMEMORIZESPECIFIC; // *dda* pattern
				else wildcardSelector |=  WILDMEMORIZEGAP; // variable gap
				if (trace & TRACE_PATTERN  && CheckTopicTrace()) Log(STDUSERLOG,(char*)"_");
				continue;
			case '@': // factset ref
				if (word[1] == '_') // set positional reference  @_20+ or @_0-   
				{
					if (firstMatched < 0) firstMatched = NORETRY; // cannot retry this match locally
	
					// memorize gap to end based on direction...xxxx
					if (gap && !reverse) // close to end of sentence 
					{
						positionStart = wordCount; // pretend to match at end of sentence
						int start = gap & 0x000000ff;
						int limit = (gap >> 8);
						gap = 0;   //   turn off
  						if ((positionStart + 1 - start) > limit) //   too long til end
						{
							matched = false;
 							wildcardSelector &= -1 ^ WILDMEMORIZEGAP;
							break;
						}
						if (wildcardSelector & WILDMEMORIZEGAP) 
						{
							SetWildCard(start,wordCount,true);  //   legal swallow of gap //   request memorize
 							wildcardSelector &= -1 ^ WILDMEMORIZEGAP;
						}
					}

					char* end = word+3;  // skip @_2
					if (IsDigit(*end)) ++end; // point to proper + or - ending
					unsigned int wild = wildcardPosition[GetWildcardID(word+1)];
					if (*end == '+') 
					{
						positionStart = WILDCARD_START(wild);
						positionEnd = WILDCARD_END(wild);
						reverse = false;
					}
					else if (*end == '-') 
					{
						reverse = true;
						positionStart = WILDCARD_END(wild);
						positionEnd = WILDCARD_START(wild); 
					}
					if (!positionEnd) break;
					oldEnd = positionEnd; // forced match ok
					oldStart = positionStart;
					if (trace & TRACE_PATTERN  && CheckTopicTrace()) 
					{
						if (positionStart <= 0 || positionStart > wordCount || positionEnd <= 0 || positionEnd > wordCount) Log(STDUSERLOG, "(index:%d)",positionEnd);
						else if (positionStart == positionEnd) Log(STDUSERLOG,(char*)"(word:%s index:%d)",wordStarts[positionEnd],positionEnd);
						else Log(STDUSERLOG,(char*)"(word:%s-%s index:%d-%d)",wordStarts[positionStart],wordStarts[positionEnd],positionStart,positionEnd);
					}
					matched = true;
				}
				else
				{
					int set = GetSetID(word);
					if (set == ILLEGAL_FACTSET) matched = false;
					else matched = FACTSET_COUNT(set) != 0;
				}
				break;
   			case '<': //   sentence start marker OR << >> set
				if (firstMatched < 0) firstMatched = NORETRY; // cannot retry this match
				if (word[1] == '<') 
					goto DOUBLELEFT; //   << 
                else 
				{
					ptr = nextTokenStart;
					if (gap && !reverse) // cannot memorize going forward to  start of sentence
					{
						gap = 0;  
						matched = false;
 						wildcardSelector &= -1 ^ WILDMEMORIZEGAP;
					}
					else { // match can FORCE it to go to start from any direction
						positionStart = positionEnd = 0; //   idiom < * and < _* handled under *
						matched = true;
					}
				}
                break;
            case '>': //   sentence end marker
				if (word[1] == '>') 
					goto DOUBLERIGHT; //   >> closer, and reset to start of sentence wild again...
				
				ptr = nextTokenStart;
				if (gap && reverse) // cannot memorize going backward to  end of sentence
				{
					gap = 0;  
					matched = false;
 					wildcardSelector &= -1 ^ WILDMEMORIZEGAP;
				}
				else if (gap || positionEnd == wordCount)// you can go to end from anywhere if you have a gap OR you are there
				{
					matched =  true;
					positionStart = positionEnd = wordCount + 1; //   pretend to match a word off end of sentence
				}
				else matched = false;
                break;
             case '*': //   GAP - accept anything (perhaps with length restrictions)
				if (word[1] == '-') //   backward gap, -1 is word before now -- BUG does not respect unmark system
				{
					int at = positionEnd - (word[2] - '0') - 1; // limited to 9 back
					if (at >= 0) //   no earlier than pre sentence start
					{
						oldEnd = at; //   set last match BEFORE our word
						positionStart = positionEnd = at + 1; //   cover the word now
						matched = true; 
					}
					else matched = false;
				}
				else if (IsDigit(word[1]))  // fixed length gap
                {
					int at;
					int count = word[1] - '0';	// how many to swallow
					if (reverse)
					{
						int begin = positionStart -1;
						at = positionStart; // start here
						while (count-- && --at >= 1) // can we swallow this (not an ignored word)
						{
							if (unmarked[at]) 
							{
								++count;	// ignore this word
								if (at == begin) --begin;	// ignore this as starter
							}
						}
						if (at >= 1 ) // pretend match
						{ 
							positionEnd = begin ; // pretend match here -  wildcard covers the gap
							positionStart = at; 
							matched = true; 
						}
						else  matched = false;
					}
					else
					{
						at = positionEnd; // start here
						int begin = positionEnd + 1;
						while (count-- && ++at <= wordCount) // can we swallow this (not an ignored word)
						{
							if (unmarked[at]) 
							{
								++count;	// ignore this word
								if (at == begin) ++begin;	// ignore this as starter
							}
						}
						if (at <= wordCount ) // pretend match
						{ 
							positionStart = begin; // pretend match here -  wildcard covers the gap
 							positionEnd = at; 
							matched = true; 
						}
						else  matched = false;
					}
                }
				else if (IsAlphaUTF8(word[1]) || word[1] == '*') 
					matched = FindPartialInSentenceTest(word+1,(positionEnd < basicStart && firstMatched < 0) ? basicStart : positionEnd,positionStart,reverse,
					positionStart,positionEnd); // wildword match like st*m* or *team* matches steamroller
                else // variable gap
                {
                    if (word[1] == '~') gap = (word[2]-'0') << 8; // *~3 - limit 9 back
                    else // I * meat
					{
						gap = 200 << 8;  // 200 is a safe infinity
						if (positionStart == 0) positionStart = INFINITE_MATCH; // < * resets to allow match anywhere
					}
                    gap |= (reverse) ? (positionStart  - 1) : (positionEnd  + 1);
					if (trace & TRACE_PATTERN  && CheckTopicTrace()) Log(STDUSERLOG,(char*)"%s ",word);
					continue;
                }
                break;
            case '$': // is user variable defined
				{
					char* val = GetUserVariable(word);
					matched = *val ? true : false;
				}
                break;
            case '^': //   function call, function argument  or indirect function variable assign ref like ^$$tmp = null
                 if  (IsDigit(word[1]) || word[1] == '$' || word[1] == '_') //   macro argument substitution or indirect function variable
                {
                    argumentText = ptr; //   transient substitution of text

					if (IsDigit(word[1]))  ptr = callArgumentList[word[1]-'0'+fnVarBase];  // nine argument limit
					else if (word[1] == '$') ptr = GetUserVariable(word+1); // get value of variable and continue in place
					else ptr = wildcardCanonicalText[GetWildcardID(word+1)]; // ordinary wildcard substituted in place (bug)?
					if (trace & TRACE_PATTERN  && CheckTopicTrace()) Log(STDUSERLOG,(char*)"%s=>",word);
					continue;
                }
                
				D = FindWord(word,0); // find the function
				if (!D || !(D->internalBits & FUNCTION_NAME)) matched = false; // shouldnt fail
				else if (D->x.codeIndex) // system function - execute it
                {
					char* old = currentOutputBase;
					char* oldrule = currentRuleOutputBase;
					currentRuleOutputBase = currentOutputBase = AllocateBuffer(); // start an independent buffer
					FunctionResult result;
					matching = true;
					ptr = DoFunction(word,ptr,currentOutputBase,result);
					matching = false;
					matched = !(result & ENDCODES); 

					// allowed to do comparisons on answers from system functions but cannot have space before them, but not from user macros
					if (*ptr == '!' && ptr[1] == ' ' ){;} // simple not operator
					else if (ptr[1] == '<' || ptr[1] == '>'){;} // << and >> are not comparison operators in a pattern
					else if (IsComparison(*ptr) && *(ptr-1) != ' ' && (*ptr != '!' || ptr[1] == '='))  // ! w/o = is not a comparison
					{
						char op[10];
						char* opptr = ptr;
						*op = *opptr;
						op[1] = 0;
						char* rhs = ++opptr; 
						if (*opptr == '=') // was == or >= or <= or &= 
						{
							op[1] = '=';
							op[2] = 0;
							++rhs;
						}
						char copy[MAX_WORD_SIZE];
						ptr = ReadCompiledWord(rhs,copy);
						rhs = copy;

						if (*rhs == '^') // local function argument or indirect ^$ var  is LHS. copy across real argument
						{
							char* at = "";
							if (rhs[1] == '$') at = GetUserVariable(rhs+1); 
							else if (IsDigit(rhs[1])) at = callArgumentList[rhs[1]-'0'+fnVarBase];
							at = SkipWhitespace(at);
							strcpy(rhs,at);
						}
				
						if (*op == '?' && opptr[0] != '~')
						{
							bool junk;
							matched = MatchTest(reverse,FindWord(currentOutputBase),
								(positionEnd < basicStart && firstMatched < 0) ? basicStart : positionEnd,NULL,NULL,false,junk,
								positionStart,positionEnd); 
							if (!(statusBits & NOT_BIT) && matched && firstMatched < 0) firstMatched = positionStart; //   first SOLID match
						}
						else
						{
							int id;
							char word1val[MAX_WORD_SIZE];
							char word2val[MAX_WORD_SIZE];
 							result = HandleRelation(currentOutputBase,op,rhs,false,id,word1val,word2val); 
							matched = (result & ENDCODES) ? 0 : 1;
						}
					}
					FreeBuffer();
					currentOutputBase = old;
					currentRuleOutputBase = oldrule;
                }
				else // user function - execute it in pattern context as continuation of current code
				{ 
					if (functionNest >= MAX_PAREN_NEST) // fail, too deep nesting
					{
						matched = false;
						break; 
					}

					//   save old base data
					baseStack[functionNest] = callArgumentBase; 
					argStack[functionNest] = callArgumentIndex; 
					fnVarBaseStack[functionNest] = fnVarBase;

					if ((trace & TRACE_PATTERN || D->internalBits & MACRO_TRACE)  && CheckTopicTrace()) Log(STDUSERLOG,(char*)"((char*)"); 
					ptr += 2; // skip ( and space
					// read arguments
					while (*ptr && *ptr != ')' ) 
					{
						char* arg = callArgumentList[callArgumentIndex++];
						ptr = ReadArgument(ptr,arg);  // gets the unevealed arg
						if ((trace & TRACE_PATTERN || D->internalBits & MACRO_TRACE)  && CheckTopicTrace()) Log(STDUSERLOG,(char*)" %s, ",arg); 
					}
					if ((trace & TRACE_PATTERN || D->internalBits & MACRO_TRACE)  && CheckTopicTrace()) Log(STDUSERLOG,(char*)")\r\n"); 
					fnVarBase = callArgumentBase = argStack[functionNest];
					ptrStack[functionNest++] = ptr+2; // skip closing paren and space
					ptr = (char*) D->w.fndefinition + 1; // continue processing within the macro, skip argument count
					oldecho = echo;
					oldtrace = trace;
					if (D->internalBits & MACRO_TRACE  && CheckTopicTrace()) 
					{
						trace = (unsigned int)-1;
						echo = true;
					}
					if (trace & TRACE_PATTERN  && CheckTopicTrace()) Log(STDUSERLOG,(char*)"%s=> ",word);
					continue;
				}
				break;
          case 0: // end of data (argument or function - never a real rule)
	           if (argumentText) // return to normal from argument substitution
                {
                    ptr = argumentText;
                    argumentText = NULL;
                    continue;
                }
                else if (functionNest > startNest) // function call end
                {
 					if (trace & TRACE_PATTERN  && CheckTopicTrace()) Log(STDUSERTABLOG,(char*)""); 
					--functionNest;
                    callArgumentIndex = argStack[functionNest]; //   end of argument list (for next argument set)
                    callArgumentBase = baseStack[functionNest]; //   base of callArgumentList
                    fnVarBase = fnVarBaseStack[functionNest];
					ptr = ptrStack[functionNest]; // continue using prior code
					trace = oldtrace;
                    echo = oldecho;
					continue;
                }
                else 
				{
					ChangeDepth(-1,(char*)"Match");
					globalDepth = startdepth;
 					return false; // shouldn't happen
				}
                break;
DOUBLELEFT:  case '(': case '[':  case '{': // nested condition (required or optional) (= consecutive  [ = choice   { = optional   << all of
				// we make << also a depth token
				ptr = nextTokenStart;
				hold = wildcardIndex;
				{
					if (wildcardSelector & WILDMEMORIZESPECIFIC) 
					{
						pendingMatch = wildcardIndex;	// on match later, use this matchvar 
						SetWildCard(1,1,true); // dummy match
					}

					int oldgap = gap;
					int returnStart = positionStart;
					int returnEnd = positionEnd;
					int rStart = positionStart;
					int rEnd = positionEnd;
					unsigned int oldselect = wildcardSelector;
					wildcardSelector = 0;
					bool uppercasemat = false;
					// nest inherits gaps leading to it. memorization requests withheld til he returns
					int whenmatched = 0;
					char* type = "[";
					if (*word == '(') type = "(";
					else if (*word == '{') type = "{";
					else if (*word == '<') 
					{
						type = "<<";
						positionEnd = startposition;  //   allowed to pick up after here - oldStart/oldEnd synch automatically works
						positionStart = INFINITE_MATCH;
						rEnd = 0;
						rStart = INFINITE_MATCH; 
					}
					matched = Match(ptr,depth+1,positionEnd,type, positionStart == INFINITE_MATCH,gap,wildcardSelector,returnStart,
						returnEnd,uppercasemat,whenmatched,positionStart,positionEnd,reverse); //   subsection ok - it is allowed to set position vars, if ! get used, they dont matter because we fail
					wildcardSelector = oldselect;
					if (matched) 
					{
						if (!(statusBits & NOT_BIT)  && firstMatched < 0) firstMatched = whenmatched;
						positionStart = returnStart;
						if (positionStart == INFINITE_MATCH && returnStart > 0 &&  returnStart != INFINITE_MATCH) positionStart = returnEnd;
						positionEnd = returnEnd;
						if (*word == '<') // allows thereafter to be anywhere
						{
							positionStart = INFINITE_MATCH;
							oldEnd = oldStart = positionEnd = 0;
						}
						if (wildcardSelector) gap = oldgap;	 // to size a gap
						uppercasematch = uppercasemat;
						// The whole thing matched but if @_ was used, which way it ran and what to consider the resulting zone is completely confused.
						// So force a tolerable meaning so it acts like it is contiguous to caller.  If we are memorizing it may be silly but its what we can do.
						if (*word == '(' && positionStart == NORETRY) 
						{
							positionEnd = positionStart = (reverse) ? (oldStart - 1) : (oldEnd + 1) ;  // claim we only moved 1 unit
						}
						else if (positionEnd) oldEnd = (reverse) ? (positionEnd + 1) : (positionEnd - 1); //   nested checked continuity, so we allow match whatever it found - but not if never set it (match didnt have words)
						if (*word == '{') 
						{
							gap = oldgap; // restore any pending gap we didnt plug  (eg *~2 {xx yy zz} a )
						}
					}
					else if (*word == '{') 
					{
						gap = oldgap; // restore any pending gap we didnt plug  (eg *~2 {xx yy zz} a )
					}
					else // no match for ( or [ or << means we have to restore old positions regardless of what happened inside
					{ // but we should check why the positions were not properly restored from the match call...BUG
						positionStart = rStart;
						positionEnd = rEnd;
					}
				}
				ptr = BalanceParen(ptr); // skip over the material including closer 
				if (!matched) // failed, revert wildcard index - if ! was used, we will need this
                {
  				    if (*word == '{') 
                    {
						if (wildcardSelector & WILDMEMORIZESPECIFIC) //   we need to memorize failure because optional cant fail
						{
							wildcardSelector ^= WILDMEMORIZESPECIFIC;
							SetWildCardGiven(0, wordCount,true,pendingMatch); 
						}
						pendingMatch = -1;
                        if (gap) continue;   //   if we are waiting to close a wildcard, ignore our failed existence entirely
                        statusBits |= NOT_BIT; //   we didnt match and pretend we didnt want to
                    }
   					else // failure of [ and ( and <<
					{
						wildcardSelector = 0;
						wildcardIndex = hold;
					}
    				pendingMatch = -1;
				}
                break;
 DOUBLERIGHT: case ')': case ']': case '}' :  //   end sequence/choice/optional

				ptr = nextTokenStart;
				matched = (*kind == '(' || *kind == '<'); //   [] and {} must be failures if we are here while ( ) and << >> are success
				if (gap) //   pending gap  -  [ foo fum * ] and { foo fot * } are pointless but [*3 *2] is not 
                {
					if (depth != 0) // for simplicity don't end with a gap 
					{
						gap = wildcardSelector = 0;
						matched = false; //   force match failure
					}
					else positionStart = wordCount + 1; //   at top level a close implies > )
				}
                break; 
            case '"':  //   double quoted string
				matched = FindPhrase(word,(positionEnd < basicStart && firstMatched < 0) ? basicStart : positionEnd, reverse,
					positionStart,positionEnd);
				if (!(statusBits & NOT_BIT) && matched && firstMatched < 0) firstMatched = positionStart; //   first SOLID match
				break;
            case '%': //   system variable
				if (!word[1]) // simple % 
				{
					bool junk;
					matched = MatchTest(reverse,FindWord(word),(positionEnd < basicStart && firstMatched < 0) ? basicStart: positionEnd,NULL,NULL,
						statusBits & QUOTE_BIT,junk,positionStart,positionEnd); //   possessive 's
					if (!(statusBits & NOT_BIT) && matched && firstMatched < 0) firstMatched = positionStart; //   first SOLID match
				}
                else matched = SysVarExists(word);
                break;
            case '?': //  question sentence? 
				ptr = nextTokenStart;
				if (!word[1]) matched = (tokenFlags & QUESTIONMARK) ? true : false;
				else matched = false;
	            break;
            case '=': //   a comparison test - never quotes the left side. Right side could be quoted
				//   format is:  = 1-bytejumpcodeToComparator leftside comparator rightside
				if (!word[1]) //   the simple = being present
				{
					bool junk;
					matched = MatchTest(reverse,FindWord(word),(positionEnd < basicStart && firstMatched < 0)  ? basicStart : positionEnd,NULL,NULL,
						statusBits & QUOTE_BIT,junk,positionStart,positionEnd); //   possessive 's
					if (!(statusBits & NOT_BIT) && matched && firstMatched < 0) firstMatched = positionStart; //   first SOLID match
				}
				//   if left side is anything but a variable $ or _ or @, it must be found in sentence and that is what we compare against
				else 
				{
					char lhsside[MAX_WORD_SIZE];
					char* lhs = lhsside;
					char op[10];
					char rhsside[MAX_WORD_SIZE];
					char* rhs = rhsside;
					DecodeComparison(word, lhs, op, rhs);
					if (trace) sprintf(word,(char*)"%s%s%s",lhs,op,rhs);
					if (*lhs == '^') DecodeFNRef(lhs); // local function arg indirect ^$ var or _ as LHS
					if (*rhs == '^') DecodeFNRef(rhs);// local function argument or indirect ^$ var  is LHS. copy across real argument
				
					bool quoted = false;
					if (*lhs == '\'') // left side is quoted
					{
						++lhs; 
						quoted = true;
					}
			
					if (*op == '?' && *rhs != '~') // NOT a ? into a set test - means does this thing exist in sentence
					{
						char* val = "";
						if (*lhs == '$') val = GetUserVariable(lhs);
						else if (*lhs == '_') val = (quoted) ? wildcardOriginalText[GetWildcardID(lhs)] : wildcardCanonicalText[GetWildcardID(lhs)];
						else if (*lhs == '^' && IsDigit(lhs[1])) val = callArgumentList[lhs[1]-'0'+fnVarBase];  // nine argument limit
						else if (*lhs == '%') val = SystemVariable(lhs,NULL);
						else val = lhs; // direct word

						if (*val == '"') // phrase requires dynamic matching
						{
							matched = FindPhrase(val,(positionEnd < basicStart && firstMatched < 0) ? basicStart : positionEnd, reverse,
								positionStart,positionEnd);
							if (!(statusBits & NOT_BIT) && matched && firstMatched < 0) firstMatched = positionStart; //   first SOLID match
							if (trace) sprintf(word,(char*)"%s(%s)%s",lhs,val,op);
							break;
						}

						bool junk;
						matched = MatchTest(reverse,FindWord(val),(positionEnd < basicStart && firstMatched < 0) ? basicStart : positionEnd,NULL,NULL,
							quoted,junk,positionStart,positionEnd); 
						if (!(statusBits & NOT_BIT) && matched && firstMatched < 0) firstMatched = positionStart; //   first SOLID match
						if (trace) sprintf(word,(char*)"%s(%s)%s",lhs,val,op);
						break;
					}
	
					result = *lhs;
					if (result == '%' || result == '$' || result == '_' || result == '@' || (*op == '?' && rhs)) // otherwise for words and concepts, look up in sentence and check relation there
					{
						if (result == '_' && quoted) --lhs; // include the quote
						char word1val[MAX_WORD_SIZE];
						char word2val[MAX_WORD_SIZE];
						FunctionResult answer = HandleRelation(lhs,op,rhs,false,id,word1val,word2val); 
						matched = (answer & ENDCODES) ? 0 : 1;
						if (trace) 
						{
							if (!stricmp(lhs,word1val)) *word1val = 0; // dont need redundant constants in trace
							if (!stricmp(rhs,word2val)) *word2val = 0; // dont need redundant constants in trace
							if (*word1val && *word2val) sprintf(word,(char*)"%s(%s)%s%s(%s)",lhs,word1val,op,rhs,word2val);
							else if (*word1val) sprintf(word,(char*)"%s(%s)%s%s",lhs,word1val,op,rhs);
							else if (*word2val) sprintf(word,(char*)"%s%s%s(%s)",lhs,op,rhs,word2val);
							else sprintf(word,(char*)"%s%s%s",lhs,op,rhs);
						}
					}
					else // find and test
					{
						bool junk;
						matched = MatchTest(reverse,FindWord(lhs),(positionEnd < basicStart && firstMatched < 0) ? basicStart : positionEnd,op,rhs,
							quoted,junk,positionStart,positionEnd); //   MUST match later 
						if (!matched) break;
					}
 				}
				break;
            case '\\': //   escape to allow [ ] () < > ' {  } ! as words and 's possessive And anything else for that matter
				{
					bool junk;
					matched =  MatchTest(reverse,FindWord(word+1),(positionEnd < basicStart && firstMatched < 0) ? basicStart : positionEnd,NULL,NULL,
						statusBits & QUOTE_BIT,junk,positionStart,positionEnd);
					if (!(statusBits & NOT_BIT) && matched && firstMatched < 0) firstMatched = positionStart; 
					if (matched) {}
					else if (word[1] == '!' ) matched =  (wordCount && (tokenFlags & EXCLAMATIONMARK)); //   exclamatory sentence
  					else if (word[1] == '?') matched =  (tokenFlags & QUESTIONMARK) ? true : false; //   question sentence
					break;
				}
			case '~': // current topic ~ and named topic
				if (word[1] == 0) // current topic
				{
					matched = IsCurrentTopic(currentTopicID); // clearly we are executing rules from it but is the current topic interesting
					break;
				}
				// drop thru for all other ~
			default: //   ordinary words, concept/topic, numbers, : and ~ and | and & accelerator
				matched = MatchTest(reverse,FindWord(word),(positionEnd < basicStart && firstMatched < 0) ? basicStart : positionEnd,NULL,NULL,
					statusBits & QUOTE_BIT,uppercasematch,positionStart,positionEnd);
				if (!matched || !(wildcardSelector & WILDMEMORIZESPECIFIC)) uppercasematch = false;
				if (!(statusBits & NOT_BIT) && matched && firstMatched < 0) firstMatched = positionStart;
         }