ChipMaster's bwBASIC This also includes history going back to v2.10. *WARN* some binary files might have been corrupted by CRLF.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

763 lines
17 KiB

  1. /***************************************************************
  2. bwb_str.c String-Management Routines
  3. for Bywater BASIC Interpreter
  4. Copyright (c) 1993, Ted A. Campbell
  5. Bywater Software
  6. email: tcamp@delphi.com
  7. Copyright and Permissions Information:
  8. All U.S. and international rights are claimed by the author,
  9. Ted A. Campbell.
  10. This software is released under the terms of the GNU General
  11. Public License (GPL), which is distributed with this software
  12. in the file "COPYING". The GPL specifies the terms under
  13. which users may copy and use the software in this distribution.
  14. A separate license is available for commercial distribution,
  15. for information on which you should contact the author.
  16. ***************************************************************/
  17. /*---------------------------------------------------------------*/
  18. /* NOTE: Modifications marked "JBV" were made by Jon B. Volkoff, */
  19. /* 11/1995 (eidetics@cerf.net). */
  20. /* */
  21. /* Those additionally marked with "DD" were at the suggestion of */
  22. /* Dale DePriest (daled@cadence.com). */
  23. /* */
  24. /* Version 3.00 by Howard Wulf, AF5NE */
  25. /* */
  26. /* Version 3.10 by Howard Wulf, AF5NE */
  27. /* */
  28. /* Version 3.20 by Howard Wulf, AF5NE */
  29. /* */
  30. /*---------------------------------------------------------------*/
  31. /***************************************************************
  32. BASIC allows embedded NUL (0) characters. C str*() does not.
  33. ALL the StringType code should use mem*() and ->length.
  34. ALL the StringType code should prevent string overflow.
  35. ***************************************************************/
  36. #include "bwbasic.h"
  37. static int CharListToSet (char *pattern, int start, int stop);
  38. static int IndexOf (char *buffer, char find, int start);
  39. /***************************************************************
  40. FUNCTION: str_btob()
  41. DESCRIPTION: This C function assigns a bwBASIC string
  42. structure to another bwBASIC string
  43. structure.
  44. ***************************************************************/
  45. int
  46. str_btob (StringType * d, StringType * s)
  47. {
  48. assert (d != NULL);
  49. assert (s != NULL);
  50. /* assert( s->length >= 0 ); */
  51. assert( My != NULL );
  52. if (s->length > MAXLEN)
  53. {
  54. WARN_STRING_TOO_LONG; /* str_btob */
  55. s->length = MAXLEN;
  56. }
  57. /* get memory for new buffer */
  58. if (d->sbuffer != NULL)
  59. {
  60. /* free the destination's buffer */
  61. if (d->sbuffer == My->MaxLenBuffer)
  62. {
  63. /*
  64. ** this occurs when setting the return value of a multi-line string user function
  65. **
  66. ** FUNCTION INKEY$
  67. ** DIM A$
  68. ** LINE INPUT A$
  69. ** LET INKEY$ = LEFT$( A$, 1 )
  70. ** END FUNCTION
  71. **
  72. */
  73. }
  74. else if (d->sbuffer == My->ConsoleOutput)
  75. {
  76. /* this should never occur, but let's make sure we don't crash if it does */
  77. WARN_INTERNAL_ERROR;
  78. /* continue processing */
  79. }
  80. else if (d->sbuffer == My->ConsoleInput)
  81. {
  82. /* this should never occur, but let's make sure we don't crash if it does */
  83. WARN_INTERNAL_ERROR;
  84. /* continue processing */
  85. }
  86. else
  87. {
  88. free (d->sbuffer);
  89. d->sbuffer = NULL;
  90. }
  91. d->sbuffer = NULL;
  92. d->length = 0;
  93. }
  94. if (d->sbuffer == NULL)
  95. {
  96. /* allocate the destination's buffer */
  97. d->length = 0;
  98. if ((d->sbuffer =
  99. (char *) calloc (s->length + 1 /* NulChar */ ,
  100. sizeof (char))) == NULL)
  101. {
  102. WARN_OUT_OF_MEMORY;
  103. return FALSE;
  104. }
  105. }
  106. /* write the b string to the b string */
  107. assert( d->sbuffer != NULL );
  108. if( s->length > 0 )
  109. {
  110. assert( s->sbuffer != NULL );
  111. bwb_memcpy (d->sbuffer, s->sbuffer, s->length);
  112. }
  113. d->length = s->length;
  114. d->sbuffer[d->length] = NulChar;
  115. return TRUE;
  116. }
  117. /***************************************************************
  118. FUNCTION: str_cmp()
  119. DESCRIPTION: This C function performs the equivalent
  120. of the C strcmp() function, using BASIC
  121. strings.
  122. ***************************************************************/
  123. int
  124. str_cmp (StringType * a, StringType * b)
  125. {
  126. assert (a != NULL);
  127. assert (b != NULL);
  128. assert( My != NULL );
  129. assert( My->CurrentVersion != NULL );
  130. if (a->length > MAXLEN)
  131. {
  132. WARN_STRING_TOO_LONG; /* str_cmp */
  133. a->length = MAXLEN;
  134. }
  135. if (b->length > MAXLEN)
  136. {
  137. WARN_STRING_TOO_LONG; /* str_cmp */
  138. b->length = MAXLEN;
  139. }
  140. if (a->sbuffer == NULL)
  141. {
  142. if (b->sbuffer == NULL)
  143. {
  144. return 0;
  145. }
  146. if (b->length == 0)
  147. {
  148. return 0;
  149. }
  150. return 1;
  151. }
  152. assert( a->sbuffer != NULL );
  153. a->sbuffer[a->length] = NulChar;
  154. if (b->sbuffer == NULL)
  155. {
  156. if (a->sbuffer == NULL)
  157. {
  158. return 0;
  159. }
  160. if (a->length == 0)
  161. {
  162. return 0;
  163. }
  164. return -1;
  165. }
  166. assert( b->sbuffer != NULL );
  167. b->sbuffer[b->length] = NulChar;
  168. if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
  169. {
  170. /* case insensitive */
  171. return bwb_stricmp (a->sbuffer, b->sbuffer); /* NOTE: embedded NUL characters terminate comparison */
  172. }
  173. else
  174. {
  175. /* case sensitive */
  176. return bwb_strcmp (a->sbuffer, b->sbuffer); /* NOTE: embedded NUL characters terminate comparison */
  177. }
  178. }
  179. /***************************************************************
  180. MATCH
  181. ***************************************************************/
  182. int
  183. str_match (char *A, int A_Length, char *B, int B_Length, int I_Start)
  184. {
  185. /*
  186. SYNTAX: j% = MATCH( a$, b$, i% )
  187. MATCH returns the position of the first occurrence of a$ in b$
  188. starting with the character position given by the third parameter.
  189. A zero is returned if no MATCH is found.
  190. The following pattern-matching features are available:
  191. # matches any digit (0-9).
  192. ! matches any upper-or lower-case letter.
  193. ? matches any character.
  194. \ serves as an escape character indicating the following character does not have special meaning.
  195. For example, a ? signifies any character is a MATCH unless preceded by a \.
  196. a$ and b$ must be strings.
  197. If either of these arguments are numeric, an error occurs.
  198. If i% is real, it is converted to an integer.
  199. If i% is a string, an error occurs.
  200. If i% is negative or zero, a run-time error occurs.
  201. When i% is greater than the length of b$, zero is returned.
  202. If b$ is a null string, a 0 is returned.
  203. If b$ is not null, but a$ is null, a 1 is returned.
  204. Examples:
  205. MATCH( "is", "Now is the", 1) returns 5
  206. MATCH( "##", "October 8, 1876", 1) returns 12
  207. MATCH( "a?", "character", 4 ) returns 5
  208. MATCH( "\#", "123#45", 1) returns 4
  209. MATCH( "ABCD", "ABC", 1 ) returns 0
  210. MATCH( "\#1\\\?", "1#1\?2#", 1 ) returns 2
  211. */
  212. int a; /* current position in A$ */
  213. int b; /* current position in B$ */
  214. assert (A != NULL);
  215. assert (B != NULL);
  216. if (I_Start <= 0)
  217. {
  218. return 0;
  219. }
  220. if (I_Start > B_Length)
  221. {
  222. return 0;
  223. }
  224. if (B_Length <= 0)
  225. {
  226. return 0;
  227. }
  228. if (A_Length <= 0)
  229. {
  230. return 1;
  231. }
  232. I_Start--; /* BASIC to C */
  233. for (b = I_Start; b < B_Length; b++)
  234. {
  235. int n; /* number of characters in A$ matched with B$ */
  236. n = 0;
  237. for (a = 0; a < A_Length; a++)
  238. {
  239. int bn;
  240. bn = b + n;
  241. if (A[a] == '#' && bwb_isdigit (B[bn]))
  242. {
  243. n++;
  244. }
  245. else if (A[a] == '!' && bwb_isalpha (B[bn]))
  246. {
  247. n++;
  248. }
  249. else if (A[a] == '?')
  250. {
  251. n++;
  252. }
  253. else if (a < (A_Length - 1) && A[a] == '\\' && A[a + 1] == B[bn])
  254. {
  255. n++;
  256. a++;
  257. }
  258. else if (A[a] == B[bn])
  259. {
  260. n++;
  261. }
  262. else
  263. {
  264. break;
  265. }
  266. }
  267. if (a == A_Length)
  268. {
  269. b++; /* C to BASIC */
  270. return b;
  271. }
  272. }
  273. return 0;
  274. }
  275. /***************************************************************
  276. FUNCTION: str_like()
  277. DESCRIPTION: This C function performs the equivalent
  278. of the BASIC LIKE operator, using BASIC
  279. strings.
  280. ***************************************************************/
  281. /*
  282. inspired by http://www.blackbeltcoder.com/Articles/net/implementing-vbs-like-operator-in-c
  283. */
  284. /*
  285. KNOWN ISSUES:
  286. To match the character '[', use "[[]".
  287. To match the character '?', use "[?]".
  288. To match the character '*', use "[*]".
  289. Does not match "" with "[]" or "[!]".
  290. */
  291. #define CHAR_SET '*'
  292. #define CHAR_CLR ' '
  293. static char charList[256];
  294. static int
  295. IndexOf (char *buffer, char find, int start)
  296. {
  297. int buffer_count;
  298. int buffer_length;
  299. assert (buffer != NULL);
  300. assert( My != NULL );
  301. assert( My->CurrentVersion != NULL );
  302. buffer_length = bwb_strlen (buffer);
  303. if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
  304. {
  305. find = bwb_toupper (find);
  306. }
  307. for (buffer_count = start; buffer_count < buffer_length; buffer_count++)
  308. {
  309. char theChar;
  310. theChar = buffer[buffer_count];
  311. if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
  312. {
  313. theChar = bwb_toupper (theChar);
  314. }
  315. if (theChar == find)
  316. {
  317. /* FOUND */
  318. return buffer_count;
  319. }
  320. }
  321. /* NOT FOUND */
  322. return -1;
  323. }
  324. static int
  325. CharListToSet (char *pattern, int start, int stop)
  326. {
  327. /*
  328. Converts a string of characters to a HashSet of characters. If the string
  329. contains character ranges, such as A-Z, all characters in the range are
  330. also added to the returned set of characters.
  331. */
  332. int pattern_Count;
  333. assert (pattern != NULL);
  334. bwb_memset (charList, CHAR_CLR, 256);
  335. if (start > stop)
  336. {
  337. /* ERROR */
  338. return FALSE;
  339. }
  340. /* Leading '-' */
  341. while (pattern[start] == '-')
  342. {
  343. /* Match character '-' */
  344. charList[0x00FF & pattern[start]] = CHAR_SET;
  345. start++;
  346. if (start > stop)
  347. {
  348. /* DONE */
  349. return TRUE;
  350. }
  351. }
  352. /* Trailing '-' */
  353. while (pattern[stop] == '-')
  354. {
  355. /* Match character '-' */
  356. charList[0x00FF & pattern[stop]] = CHAR_SET;
  357. stop--;
  358. if (start > stop)
  359. {
  360. /* DONE */
  361. return TRUE;
  362. }
  363. }
  364. for (pattern_Count = start; pattern_Count <= stop; pattern_Count++)
  365. {
  366. if (pattern[pattern_Count] == '-')
  367. {
  368. /* Character range */
  369. char startChar;
  370. char endChar;
  371. char theChar;
  372. if (pattern_Count > start)
  373. {
  374. startChar = pattern[pattern_Count - 1];
  375. if (startChar == '-')
  376. {
  377. /* ERROR */
  378. return FALSE;
  379. }
  380. if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
  381. {
  382. startChar = bwb_toupper (startChar);
  383. }
  384. }
  385. else
  386. {
  387. /* ERROR */
  388. return FALSE;
  389. }
  390. if (pattern_Count < stop)
  391. {
  392. endChar = pattern[pattern_Count + 1];
  393. if (endChar == '-')
  394. {
  395. /* ERROR */
  396. return FALSE;
  397. }
  398. if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
  399. {
  400. endChar = bwb_toupper (endChar);
  401. }
  402. if (endChar < startChar)
  403. {
  404. /* ERROR */
  405. return FALSE;
  406. }
  407. }
  408. else
  409. {
  410. /* ERROR */
  411. return FALSE;
  412. }
  413. /*
  414. Although the startChar has already been set,
  415. and the endChar will be set on the next loop,
  416. we go ahead and set them here too.
  417. Not the most efficient, but easy to understand,
  418. and we do not have to do anything special
  419. for edge cases such as [A-A] and [A-B].
  420. */
  421. for (theChar = startChar; theChar <= endChar; theChar++)
  422. {
  423. charList[0x00FF & theChar] = CHAR_SET;
  424. }
  425. }
  426. else
  427. {
  428. /* Single character */
  429. char theChar;
  430. theChar = pattern[pattern_Count];
  431. if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
  432. {
  433. theChar = bwb_toupper (theChar);
  434. }
  435. charList[0x00FF & theChar] = CHAR_SET;
  436. }
  437. }
  438. return TRUE;
  439. }
  440. int
  441. IsLike (char *buffer, int *buffer_count, int buffer_Length,
  442. char *pattern, int *pattern_count, int pattern_Length)
  443. {
  444. /* Implement's VB's Like operator logic. */
  445. /*
  446. if matched then
  447. buffer_count is updated
  448. pattern_count is updated
  449. returns TRUE
  450. else
  451. returns FALSE
  452. end if
  453. */
  454. int bc;
  455. int pc;
  456. assert (buffer != NULL);
  457. assert (buffer_count != NULL);
  458. assert (pattern != NULL);
  459. assert (pattern_count != NULL);
  460. assert( My != NULL );
  461. assert( My->CurrentVersion != NULL );
  462. bc = *buffer_count;
  463. pc = *pattern_count;
  464. /* Loop through pattern string */
  465. while (pc < pattern_Length)
  466. {
  467. /* Get next pattern character */
  468. if (pattern[pc] == '[')
  469. {
  470. /* Character list */
  471. /* [] and [!] are special */
  472. char IsExclude;
  473. IsExclude = CHAR_CLR;
  474. pc++;
  475. /* pc is first character after '[' */
  476. if (pattern[pc] == '!')
  477. {
  478. pc++;
  479. IsExclude = CHAR_SET;
  480. }
  481. /* pc is first character after '[' */
  482. if (pattern[pc] == ']')
  483. {
  484. /* [] and [!] are special */
  485. /* pc is first character after '[' and is a ']' */
  486. pc++;
  487. if (IsExclude == CHAR_CLR)
  488. {
  489. /* [] */
  490. /* matches "" */
  491. }
  492. else
  493. {
  494. /* [!] */
  495. /* same as '?' */
  496. if (bc >= buffer_Length)
  497. {
  498. /* we have completed the buffer without completing the pattern */
  499. return FALSE;
  500. }
  501. bc++;
  502. }
  503. }
  504. else
  505. {
  506. /* Build character list */
  507. /* pc is first character after '[' and is not a ']' */
  508. int stop_count;
  509. stop_count = IndexOf (pattern, ']', pc);
  510. /* stop_count is the character ']' */
  511. if (stop_count < 0)
  512. {
  513. /* NOT FOUND */
  514. return FALSE;
  515. }
  516. /* pc is first character after '[' */
  517. /* stop_count is the character ']' */
  518. CharListToSet (pattern, pc, stop_count - 1);
  519. pc = stop_count + 1;
  520. /* pc is first character after ']' */
  521. if (bc >= buffer_Length)
  522. {
  523. /* we have completed the buffer without completing the pattern */
  524. return FALSE;
  525. }
  526. if (charList[0x00FF & buffer[bc]] == IsExclude)
  527. {
  528. /* not matched */
  529. return FALSE;
  530. }
  531. bc++;
  532. }
  533. }
  534. else if (pattern[pc] == '?' /* LIKE char */ )
  535. {
  536. /* Matches a single character */
  537. pc++;
  538. if (bc >= buffer_Length)
  539. {
  540. /* Check for end of string */
  541. /* we have completed the buffer without completing the pattern */
  542. return FALSE;
  543. }
  544. bc++;
  545. }
  546. else if (pattern[pc] == '#' /* LIKE digit */ )
  547. {
  548. /* Matches a single digit */
  549. pc++;
  550. if (bc >= buffer_Length)
  551. {
  552. /* Check for end of string */
  553. /* we have completed the buffer without completing the pattern */
  554. return FALSE;
  555. }
  556. if (bwb_isdigit (buffer[bc]))
  557. {
  558. bc++;
  559. }
  560. else
  561. {
  562. /* not matched */
  563. return FALSE;
  564. }
  565. }
  566. else if (pattern[pc] == '*' /* LIKE chars */ )
  567. {
  568. /* Zero or more characters */
  569. while (pattern[pc] == '*' /* LIKE chars */ )
  570. {
  571. pc++;
  572. }
  573. if (pc == pattern_Length)
  574. {
  575. /* Matches all remaining characters */
  576. bc = buffer_Length;
  577. pc = pattern_Length;
  578. break;
  579. }
  580. else
  581. {
  582. int p;
  583. int b;
  584. int IsMatched;
  585. p = pc;
  586. b = bc;
  587. IsMatched = FALSE;
  588. while (b <= buffer_Length && IsMatched == FALSE)
  589. {
  590. int last_b;
  591. last_b = b;
  592. IsMatched =
  593. IsLike (buffer, &b, buffer_Length, pattern, &p, pattern_Length);
  594. if (IsMatched == FALSE)
  595. {
  596. /* not matched, try again begining at next buffer position */
  597. p = pc;
  598. b = last_b + 1;
  599. }
  600. }
  601. if (IsMatched == FALSE)
  602. {
  603. /* not matched */
  604. return FALSE;
  605. }
  606. pc = p;
  607. bc = b;
  608. }
  609. }
  610. else
  611. {
  612. char pattChar;
  613. char buffChar;
  614. pattChar = pattern[pc];
  615. if (bc >= buffer_Length)
  616. {
  617. /* Check for end of string */
  618. /* we have completed the buffer without completing the pattern */
  619. return FALSE;
  620. }
  621. buffChar = buffer[bc];
  622. if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
  623. {
  624. pattChar = bwb_toupper (pattChar);
  625. buffChar = bwb_toupper (buffChar);
  626. }
  627. if (pattChar == buffChar)
  628. {
  629. /* matched specified character */
  630. pc++;
  631. bc++;
  632. }
  633. else
  634. {
  635. /* not matched */
  636. return FALSE;
  637. }
  638. }
  639. }
  640. /* Return true if all characters matched */
  641. if (pc < pattern_Length)
  642. {
  643. /* not matched */
  644. return FALSE;
  645. }
  646. if (bc < buffer_Length)
  647. {
  648. /* not matched */
  649. return FALSE;
  650. }
  651. /* all characters matched */
  652. *buffer_count = bc;
  653. *pattern_count = pc;
  654. return TRUE;
  655. }
  656. /* EOF */