- /***************************************************************
-
- bwb_str.c String-Management Routines
- for Bywater BASIC Interpreter
-
- Copyright (c) 1993, Ted A. Campbell
- Bywater Software
-
- email: tcamp@delphi.com
-
- Copyright and Permissions Information:
-
- All U.S. and international rights are claimed by the author,
- Ted A. Campbell.
-
- This software is released under the terms of the GNU General
- Public License (GPL), which is distributed with this software
- in the file "COPYING". The GPL specifies the terms under
- which users may copy and use the software in this distribution.
-
- A separate license is available for commercial distribution,
- for information on which you should contact the author.
-
- ***************************************************************/
-
- /*---------------------------------------------------------------*/
- /* NOTE: Modifications marked "JBV" were made by Jon B. Volkoff, */
- /* 11/1995 (eidetics@cerf.net). */
- /* */
- /* Those additionally marked with "DD" were at the suggestion of */
- /* Dale DePriest (daled@cadence.com). */
- /* */
- /* Version 3.00 by Howard Wulf, AF5NE */
- /* */
- /* Version 3.10 by Howard Wulf, AF5NE */
- /* */
- /* Version 3.20 by Howard Wulf, AF5NE */
- /* */
- /*---------------------------------------------------------------*/
-
-
-
- /***************************************************************
-
- BASIC allows embedded NUL (0) characters. C str*() does not.
-
- ALL the StringType code should use mem*() and ->length.
- ALL the StringType code should prevent string overflow.
-
- ***************************************************************/
-
-
-
-
- #include "bwbasic.h"
-
-
- static int CharListToSet (char *pattern, int start, int stop);
- static int IndexOf (char *buffer, char find, int start);
-
-
- /***************************************************************
-
- FUNCTION: str_btob()
-
- DESCRIPTION: This C function assigns a bwBASIC string
- structure to another bwBASIC string
- structure.
-
- ***************************************************************/
-
- int
- str_btob (StringType * d, StringType * s)
- {
-
- assert (d != NULL);
- assert (s != NULL);
- /* assert( s->length >= 0 ); */
- assert( My != NULL );
-
- if (s->length > MAXLEN)
- {
- WARN_STRING_TOO_LONG; /* str_btob */
- s->length = MAXLEN;
- }
-
- /* get memory for new buffer */
- if (d->sbuffer != NULL)
- {
- /* free the destination's buffer */
- if (d->sbuffer == My->MaxLenBuffer)
- {
- /*
- ** this occurs when setting the return value of a multi-line string user function
- **
- ** FUNCTION INKEY$
- ** DIM A$
- ** LINE INPUT A$
- ** LET INKEY$ = LEFT$( A$, 1 )
- ** END FUNCTION
- **
- */
- }
- else if (d->sbuffer == My->ConsoleOutput)
- {
- /* this should never occur, but let's make sure we don't crash if it does */
- WARN_INTERNAL_ERROR;
- /* continue processing */
- }
- else if (d->sbuffer == My->ConsoleInput)
- {
- /* this should never occur, but let's make sure we don't crash if it does */
- WARN_INTERNAL_ERROR;
- /* continue processing */
- }
- else
- {
- free (d->sbuffer);
- d->sbuffer = NULL;
- }
- d->sbuffer = NULL;
- d->length = 0;
- }
- if (d->sbuffer == NULL)
- {
- /* allocate the destination's buffer */
- d->length = 0;
- if ((d->sbuffer =
- (char *) calloc (s->length + 1 /* NulChar */ ,
- sizeof (char))) == NULL)
- {
- WARN_OUT_OF_MEMORY;
- return FALSE;
- }
- }
- /* write the b string to the b string */
- assert( d->sbuffer != NULL );
- if( s->length > 0 )
- {
- assert( s->sbuffer != NULL );
- bwb_memcpy (d->sbuffer, s->sbuffer, s->length);
- }
- d->length = s->length;
- d->sbuffer[d->length] = NulChar;
- return TRUE;
- }
-
-
- /***************************************************************
-
- FUNCTION: str_cmp()
-
- DESCRIPTION: This C function performs the equivalent
- of the C strcmp() function, using BASIC
- strings.
-
- ***************************************************************/
-
- int
- str_cmp (StringType * a, StringType * b)
- {
-
- assert (a != NULL);
- assert (b != NULL);
- assert( My != NULL );
- assert( My->CurrentVersion != NULL );
-
- if (a->length > MAXLEN)
- {
- WARN_STRING_TOO_LONG; /* str_cmp */
- a->length = MAXLEN;
- }
- if (b->length > MAXLEN)
- {
- WARN_STRING_TOO_LONG; /* str_cmp */
- b->length = MAXLEN;
- }
- if (a->sbuffer == NULL)
- {
- if (b->sbuffer == NULL)
- {
- return 0;
- }
- if (b->length == 0)
- {
- return 0;
- }
- return 1;
- }
- assert( a->sbuffer != NULL );
- a->sbuffer[a->length] = NulChar;
-
- if (b->sbuffer == NULL)
- {
- if (a->sbuffer == NULL)
- {
- return 0;
- }
- if (a->length == 0)
- {
- return 0;
- }
- return -1;
- }
- assert( b->sbuffer != NULL );
- b->sbuffer[b->length] = NulChar;
-
- if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
- {
- /* case insensitive */
- return bwb_stricmp (a->sbuffer, b->sbuffer); /* NOTE: embedded NUL characters terminate comparison */
- }
- else
- {
- /* case sensitive */
- return bwb_strcmp (a->sbuffer, b->sbuffer); /* NOTE: embedded NUL characters terminate comparison */
- }
- }
-
- /***************************************************************
-
- MATCH
-
- ***************************************************************/
-
- int
- str_match (char *A, int A_Length, char *B, int B_Length, int I_Start)
- {
- /*
- SYNTAX: j% = MATCH( a$, b$, i% )
-
- MATCH returns the position of the first occurrence of a$ in b$
- starting with the character position given by the third parameter.
- A zero is returned if no MATCH is found.
-
- The following pattern-matching features are available:
- # matches any digit (0-9).
- ! matches any upper-or lower-case letter.
- ? matches any character.
- \ serves as an escape character indicating the following character does not have special meaning.
-
- For example, a ? signifies any character is a MATCH unless preceded by a \.
- a$ and b$ must be strings.
- If either of these arguments are numeric, an error occurs.
- If i% is real, it is converted to an integer.
- If i% is a string, an error occurs.
- If i% is negative or zero, a run-time error occurs.
- When i% is greater than the length of b$, zero is returned.
- If b$ is a null string, a 0 is returned.
- If b$ is not null, but a$ is null, a 1 is returned.
-
- Examples:
- MATCH( "is", "Now is the", 1) returns 5
- MATCH( "##", "October 8, 1876", 1) returns 12
- MATCH( "a?", "character", 4 ) returns 5
- MATCH( "\#", "123#45", 1) returns 4
- MATCH( "ABCD", "ABC", 1 ) returns 0
- MATCH( "\#1\\\?", "1#1\?2#", 1 ) returns 2
- */
-
-
- int a; /* current position in A$ */
- int b; /* current position in B$ */
-
- assert (A != NULL);
- assert (B != NULL);
-
-
- if (I_Start <= 0)
- {
- return 0;
- }
- if (I_Start > B_Length)
- {
- return 0;
- }
- if (B_Length <= 0)
- {
- return 0;
- }
- if (A_Length <= 0)
- {
- return 1;
- }
- I_Start--; /* BASIC to C */
- for (b = I_Start; b < B_Length; b++)
- {
- int n; /* number of characters in A$ matched with B$ */
-
- n = 0;
- for (a = 0; a < A_Length; a++)
- {
- int bn;
- bn = b + n;
- if (A[a] == '#' && bwb_isdigit (B[bn]))
- {
- n++;
- }
- else if (A[a] == '!' && bwb_isalpha (B[bn]))
- {
- n++;
- }
- else if (A[a] == '?')
- {
- n++;
- }
- else if (a < (A_Length - 1) && A[a] == '\\' && A[a + 1] == B[bn])
- {
- n++;
- a++;
- }
- else if (A[a] == B[bn])
- {
- n++;
- }
- else
- {
- break;
- }
- }
- if (a == A_Length)
- {
- b++; /* C to BASIC */
- return b;
- }
- }
- return 0;
- }
-
-
-
- /***************************************************************
-
- FUNCTION: str_like()
-
- DESCRIPTION: This C function performs the equivalent
- of the BASIC LIKE operator, using BASIC
- strings.
-
- ***************************************************************/
-
- /*
- inspired by http://www.blackbeltcoder.com/Articles/net/implementing-vbs-like-operator-in-c
- */
-
- /*
- KNOWN ISSUES:
- To match the character '[', use "[[]".
- To match the character '?', use "[?]".
- To match the character '*', use "[*]".
- Does not match "" with "[]" or "[!]".
- */
-
- #define CHAR_SET '*'
- #define CHAR_CLR ' '
-
- static char charList[256];
-
- static int
- IndexOf (char *buffer, char find, int start)
- {
- int buffer_count;
- int buffer_length;
-
- assert (buffer != NULL);
- assert( My != NULL );
- assert( My->CurrentVersion != NULL );
-
- buffer_length = bwb_strlen (buffer);
-
- if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
- {
- find = bwb_toupper (find);
- }
-
- for (buffer_count = start; buffer_count < buffer_length; buffer_count++)
- {
- char theChar;
-
- theChar = buffer[buffer_count];
-
- if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
- {
- theChar = bwb_toupper (theChar);
- }
-
-
- if (theChar == find)
- {
- /* FOUND */
- return buffer_count;
- }
-
- }
- /* NOT FOUND */
- return -1;
- }
-
-
- static int
- CharListToSet (char *pattern, int start, int stop)
- {
- /*
- Converts a string of characters to a HashSet of characters. If the string
- contains character ranges, such as A-Z, all characters in the range are
- also added to the returned set of characters.
- */
- int pattern_Count;
-
- assert (pattern != NULL);
-
- bwb_memset (charList, CHAR_CLR, 256);
-
- if (start > stop)
- {
- /* ERROR */
- return FALSE;
- }
- /* Leading '-' */
- while (pattern[start] == '-')
- {
- /* Match character '-' */
-
- charList[0x00FF & pattern[start]] = CHAR_SET;
- start++;
- if (start > stop)
- {
- /* DONE */
- return TRUE;
- }
- }
- /* Trailing '-' */
- while (pattern[stop] == '-')
- {
- /* Match character '-' */
- charList[0x00FF & pattern[stop]] = CHAR_SET;
- stop--;
- if (start > stop)
- {
- /* DONE */
- return TRUE;
- }
- }
-
-
- for (pattern_Count = start; pattern_Count <= stop; pattern_Count++)
- {
- if (pattern[pattern_Count] == '-')
- {
- /* Character range */
-
- char startChar;
- char endChar;
- char theChar;
-
- if (pattern_Count > start)
- {
- startChar = pattern[pattern_Count - 1];
- if (startChar == '-')
- {
- /* ERROR */
- return FALSE;
- }
- if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
- {
- startChar = bwb_toupper (startChar);
- }
- }
- else
- {
- /* ERROR */
- return FALSE;
- }
- if (pattern_Count < stop)
- {
- endChar = pattern[pattern_Count + 1];
- if (endChar == '-')
- {
- /* ERROR */
- return FALSE;
- }
- if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
- {
- endChar = bwb_toupper (endChar);
- }
- if (endChar < startChar)
- {
- /* ERROR */
- return FALSE;
- }
- }
- else
- {
- /* ERROR */
- return FALSE;
- }
- /*
- Although the startChar has already been set,
- and the endChar will be set on the next loop,
- we go ahead and set them here too.
- Not the most efficient, but easy to understand,
- and we do not have to do anything special
- for edge cases such as [A-A] and [A-B].
- */
- for (theChar = startChar; theChar <= endChar; theChar++)
- {
- charList[0x00FF & theChar] = CHAR_SET;
- }
- }
- else
- {
- /* Single character */
- char theChar;
-
- theChar = pattern[pattern_Count];
- if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
- {
- theChar = bwb_toupper (theChar);
- }
- charList[0x00FF & theChar] = CHAR_SET;
- }
- }
-
- return TRUE;
- }
-
-
-
- int
- IsLike (char *buffer, int *buffer_count, int buffer_Length,
- char *pattern, int *pattern_count, int pattern_Length)
- {
-
- /* Implement's VB's Like operator logic. */
-
- /*
- if matched then
- buffer_count is updated
- pattern_count is updated
- returns TRUE
- else
- returns FALSE
- end if
- */
-
- int bc;
- int pc;
-
- assert (buffer != NULL);
- assert (buffer_count != NULL);
- assert (pattern != NULL);
- assert (pattern_count != NULL);
- assert( My != NULL );
- assert( My->CurrentVersion != NULL );
-
- bc = *buffer_count;
- pc = *pattern_count;
-
-
- /* Loop through pattern string */
- while (pc < pattern_Length)
- {
-
- /* Get next pattern character */
- if (pattern[pc] == '[')
- {
- /* Character list */
- /* [] and [!] are special */
- char IsExclude;
-
- IsExclude = CHAR_CLR;
- pc++;
- /* pc is first character after '[' */
- if (pattern[pc] == '!')
- {
- pc++;
- IsExclude = CHAR_SET;
- }
- /* pc is first character after '[' */
- if (pattern[pc] == ']')
- {
- /* [] and [!] are special */
- /* pc is first character after '[' and is a ']' */
- pc++;
- if (IsExclude == CHAR_CLR)
- {
- /* [] */
- /* matches "" */
- }
- else
- {
- /* [!] */
- /* same as '?' */
- if (bc >= buffer_Length)
- {
- /* we have completed the buffer without completing the pattern */
- return FALSE;
- }
- bc++;
- }
- }
- else
- {
- /* Build character list */
- /* pc is first character after '[' and is not a ']' */
- int stop_count;
-
- stop_count = IndexOf (pattern, ']', pc);
- /* stop_count is the character ']' */
- if (stop_count < 0)
- {
- /* NOT FOUND */
- return FALSE;
- }
- /* pc is first character after '[' */
- /* stop_count is the character ']' */
- CharListToSet (pattern, pc, stop_count - 1);
- pc = stop_count + 1;
- /* pc is first character after ']' */
- if (bc >= buffer_Length)
- {
- /* we have completed the buffer without completing the pattern */
- return FALSE;
- }
- if (charList[0x00FF & buffer[bc]] == IsExclude)
- {
- /* not matched */
- return FALSE;
- }
- bc++;
- }
- }
- else if (pattern[pc] == '?' /* LIKE char */ )
- {
- /* Matches a single character */
- pc++;
- if (bc >= buffer_Length)
- {
- /* Check for end of string */
- /* we have completed the buffer without completing the pattern */
- return FALSE;
- }
- bc++;
- }
- else if (pattern[pc] == '#' /* LIKE digit */ )
- {
- /* Matches a single digit */
- pc++;
- if (bc >= buffer_Length)
- {
- /* Check for end of string */
- /* we have completed the buffer without completing the pattern */
- return FALSE;
- }
- if (bwb_isdigit (buffer[bc]))
- {
- bc++;
- }
- else
- {
- /* not matched */
- return FALSE;
- }
- }
- else if (pattern[pc] == '*' /* LIKE chars */ )
- {
- /* Zero or more characters */
- while (pattern[pc] == '*' /* LIKE chars */ )
- {
- pc++;
- }
- if (pc == pattern_Length)
- {
- /* Matches all remaining characters */
- bc = buffer_Length;
- pc = pattern_Length;
- break;
- }
- else
- {
- int p;
- int b;
- int IsMatched;
-
- p = pc;
- b = bc;
- IsMatched = FALSE;
- while (b <= buffer_Length && IsMatched == FALSE)
- {
- int last_b;
-
- last_b = b;
- IsMatched =
- IsLike (buffer, &b, buffer_Length, pattern, &p, pattern_Length);
- if (IsMatched == FALSE)
- {
- /* not matched, try again begining at next buffer position */
- p = pc;
- b = last_b + 1;
- }
- }
- if (IsMatched == FALSE)
- {
- /* not matched */
- return FALSE;
- }
- pc = p;
- bc = b;
- }
- }
- else
- {
- char pattChar;
- char buffChar;
-
- pattChar = pattern[pc];
- if (bc >= buffer_Length)
- {
- /* Check for end of string */
- /* we have completed the buffer without completing the pattern */
- return FALSE;
- }
- buffChar = buffer[bc];
-
- if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
- {
- pattChar = bwb_toupper (pattChar);
- buffChar = bwb_toupper (buffChar);
- }
-
- if (pattChar == buffChar)
- {
- /* matched specified character */
- pc++;
- bc++;
- }
- else
- {
- /* not matched */
- return FALSE;
- }
- }
- }
- /* Return true if all characters matched */
- if (pc < pattern_Length)
- {
- /* not matched */
- return FALSE;
- }
- if (bc < buffer_Length)
- {
- /* not matched */
- return FALSE;
- }
- /* all characters matched */
- *buffer_count = bc;
- *pattern_count = pc;
- return TRUE;
- }
-
-
- /* EOF */
|