From: urge@mcl.ucsb.edu (Scott Bronson)
Newsgroups: comp.sys.mac.programmer
Subject: To (?) Evan Shandev - my stream search source
Summary: stream search source
Keywords: stream search
Date: 14 Dec 92 05:04:11 GMT


Sorry for butchering your name, but I forgot what it was exactly.
I was forced to switch Email accounts, and in the move I lost my
entire mail queue with your name and address safely tucked
away.  I apologize for all this.

Anyway, I've decided to distribute this.  It's simply a cool
little algorithm to search an incoming stream.  If you use this
in one of your projects, I'd really like only one little thing
in return: mail me and tell me how you use this source.

My original letter follows.  It first answers why I think that
the CTB doesn't allow you to look at characters following a found
CMSearch string, then gives my solution to this problem.

--------------------------------------

You're not doing anything wrong that I know of.  The Comm Toolbox simply
doesn't let you get at any text following your search.  Here's my theory
why (but I don't know, so don't quote me to anyone important):

The connection tool transfers incoming text from the hardware buffer
(4-byte SCC buffer, 64-byte serial buffer, or whatever) to its own larger
buffer to hold until the application wants it.  While performing this
transfer, moving one character at a time, it updates its search strings
in the same manner that I have coded below.  When one of the strings
gets found, it immediately calls your callback routine, without transfer-
ring even another single character.

When you think about it, this makes sense.  You should not be able to get
at characters further on in the buffer, as there is no definition to how
many characters you would get.  It would make things substantially harder
for the connection tool, because it would always have to read x many more
characters after the string was found, and if the buffer overflowed, the
entire search string and all chars afterwards would have to be shifted
around to make room.

Not only that, but what standard would you make it?  Eight characters more
after the string found?  Someone will need ten more.  Okay then, ten.  But
what if someone needs fifty?  A hundred?  One K...  So, best to let the
application handle searching itself if it needs these special requirements.
I think the search function was only implemented to allow comm programs
to recognize automatic file transfer requests as demoed in the sample app
that came with the CTB developer's kit.

So, you're stuck without a standard and with no way to get at the next
few characters after your search string.  The only solution I see is for
you to write your own.  I did.  Here's my version (you'll have to change
all serial calls to be CTB calls, but other than that it's straightforward).

Note that all of the strings used in this code are C strings, because
they are far more buffer-friendly than Pascal strings.  Also, some of this
code was changed so I could send it to you, but it should (SHOULD--I'm
not certian that I didn't add bugs when I edited it) remain bug-free.
It is currently in use in a commercial application that I wrote, so I
hope it will work without a flaw...

// By Scott Bronson
// 
// The only structure used...
typedef struct searchrec {
	char *first, *cur;
} searchrec;

// We can search for this many strings maximum:
#define kNumSearches 16

// Global variables used...
short numsearches = 0;
long gStringFound;
searchrec search[kNumSearches];

// This is my actual idle loop.  It should
// be able to be used right out of the box.
//
// Idle process:
// % read chars into inbuff
// - strip the eighth bit
// - check against current searches
// - Strip newlines
// - Write to OldLines buffer
// - Put in our log file buffer
// % flush log file buffer

void WhatMeIdle( void )
{
	char inbuff[64], outbuff[64];
	// 64-byte input buffer.  The output buffer is used to store the 8th-bit
	// and newline stripped data that we will be displaying on screen.  The
	// search algorithm will not be hurt at all by the disappearance of the
	// ouptut buffer.
	
	register char c; // indexes through each char in the input buffer.
	long count;		// The number of characters read into inbuff.
	long j;			// The number of characters in outbuff.
	short i, l;		// Indexes for loops.
	OSErr err;		// Stores possible errors returned by OS.
	
	err = SerGetBuf( inDriverRefNum, &count );
	if( err == noErr && count > 0 ) {
		if( count > 64 ) count = 64;
		err = FSRead( inDriverRefNum, &count, inbuff );
		
		// Now inbuff is filled with the next (count) characters
		// from the incoming data stream.
		
		if( err == noErr ) {
			
			// We'll loop through every character in the buffer...
			for( i=0,j=0; i<count; i++ ) {
				c = inbuff[i] & 0x7F;
				
				// Now c contains the next 7-bit character.
				// Here is the search algorithm.  If there are
				// any searches pending, loop through them and
				// update the found character counts.
				
				if(numsearches) for( l=0; l<numsearches; l++ ) {
					if( c == *(search[l].cur++) ) {
						if( !*search[l].cur ) {
						
						    // Ah!  We found a string.  Leave its index
						    // in a global variable, clear all searches,
						    // and finish reading the current buffer.
						    // You can do whatever you want here.
						    
							gStringFound = l+1;
							numsearches = 0;
						}
					} else search[l].cur = search[l].first;
				}
				
				// If c is a newline character, we're done with it.
				if( c == '\n' ) continue;
				
				// I performed some other actions here that were removed
				// for clarity.  Now all we have to do is store c in the
				// output buffer so it will get written to our disk file.
				
				outbuff[j++] = c;
			} // end of for loop.
			
			// If we are storing the incoming data, then write it out.
			if( gLogToFile && j ) {
				FSWrite( gLogFileRef, &j, outbuff );
			}
		}
	} // else no chars to be read.	
}


// This is used to add a string to search for, same as CMAddSearch.
// The string passed to AddSearch MUST remain untouched from the time
// it is added until the time CheckStringFound returns a non-empty
// string (meaning AddSearch doesn't make a copy of the string it is
// passed--it just uses it in-place).

void AddSearch( char *cs )
{
	if( numsearches < kNumSearches ) {
		search[numsearches].cur = search[numsearches].first = cs;
		numsearches++;
	} // else search buffer is full.
}


// This is called periodically to see if a string is found.  It is passed
// the address of a buffer large enough to hold the longest of all the
// strings that are currently being searched for.  The buffer will contain
// the actual string found, or a 0-lenth string if none have been found yet.

void CheckStringFound( char *ss )
{
	if( gStringFound ) {
		strcpy( ss, search[--gStringFound].first );
	} else {
		ss[0] = 0;
	}
}

Newsgroups: comp.sys.mac.programmer