/*
   CyberRax Programming (C) 2003, 2004
   TITLE:   STRING2.H--
   DESCRIPTION:   Additional (advanced) string manipulation routines
   LAST MODIFIED:   07 Jul 2004
   PROCEDURES DEFINED IN THIS FILE:
       void STRCPYNEAR(destinyoffs,sourceoffs)
       void STRCPYTOFAR(destinyseg,destinyoffs,sourceoffs)
       void STRCPYFROMFAR(destinyoffs,sourceseg,sourceoffs)
       void STRCPYFAR(destinyseg,destinyoffs,sourceseg,sourceoffs)
       void STRINGUP(string)
       word STRINGLEN(string)
       word STRINGCMP(string1,string2)
       byte STRINGCUT(string,char,scanmode,cutmode,length)
       void ADVANCEDSTRUP(string,terminator)
       void ADVANCEDSTRDOWN(string,terminator)
       word ADVANCEDSTRLEN(string,terminator)
       byte ADVANCEDSTRCMP(string1,string2,terminator)
       void ADVANCEDSTRCPYNEAR(destofs,srcofs,terminator)
       void ADVANCEDSTRCPYTOFAR(destseg,destofs,srcofs,terminator)
       void ADVANCEDSTRCPYFROMFAR(destofs,srcseg,srcofs,terminator)
       void ADVANCEDSTRCPYFAR(destseg,destofs,srcseg,srcofs,terminator)
       void ASCIZ2DOS(string)
       void DOS2ASCIZ(string)
       word ASCIZ2WORD(ofs,base)
       word ASCIZ2WORDFAR(seg,ofs,base)
       word STR2WORD(ofs,base,size)
       word STR2WORDFAR(seg,ofs,base,size)

   STRUCTURES DEFINIED IN THIS FILE:
       <none>

   DEFINITIONS AFFECTING THIS CODE:
      insertSTRCPYNEAR		-  insert STRCPYNEAR() in STRCPYTOFAR() and
				   STRCPYFROMFAR() instead of calling
      insertADVANCEDSTRCPYNEAR	-  insert ADVANCEDSTRCPYNEAR() in
				   ADVANCEDSTRCPY[TO/FROM]FAR()
      insertASCIZ2WORD		-  insert ASCIZ2WORD() in ASCIZ2WORDFAR()
      insertSTR2WORD		-  insert STR2WORD() in  STRWORDFAR()
      insertCHARMULTIPLY	-  insert CHARMULTIPLY() in ASCIZ2WORD() and
				   STR2WORD() instead of calling
				 These definitions are for hand-optimizing the
				   code for speed/size in case the mentioned
				   procedures aren't called by themselves.

   NOTE:   this header file uses features of SPHINX/Sheker C-- and needs
	   quite some modifications to work under original SPHINX C-- 0.203
*/
/* History:

	20040707   -    cosmetical fixes
			changed the whole manual optimizing system: instead of
			   'InsertX' 'insertPROCEDURE's are used
	20040706   -    cosmetical fixes and changed some procedures from
			   'inline' to dynamic
	20040705   -    fixed bug in STR2WORD()/ASIZ2WORD(): CH was not cleared
			fixed bug in CHARMULTIPLY(): it handled only decimal
			   numbers
			started the list of changes (just for fun   :)
*/


? ifndef _STRING2_
? define _STRING2_	20040707


? ifndef _JSTUFF_
  ? include "JSTUFF.H--"
? endif

? ifndef _NUMBERS_
  ? include "NUMBERS.H--"
? endif

? ifndef _BYTES_
  ? include "BYTES.H--"
? endif
? ifdef _BYTES_ < 20040706
  ? print error "BYTES.H-- too old, release 20040706 or newer is needed   :(\n"
? endif


? warning		FALSE


? define TOO_LONG	ONE
? define NO_CHAR	TWO

? define STRINGSTART	NULL
? define STRINGEND	ONE


enum { AFTERCHAR, ATCHAR, BEFORECHAR };



inline word ADVANCEDSTRLEN ((E)AX, CL)   // ver. 1.03, small bugfix
/* Determines the length of a string terminating with 0 or user
      specified character.
   Differs from standard STRLEN() by accepting user input also as terminator
   Input:  DS:AX = string
	      CL = terminator
   Output: AX = length of string, not including terminator.
	   DX = type of terminator => 0 - '0'
				      1 - user specified character
	   BX, CL destroyed
   Size:   min. 19 bytes
*/
{
	DX = NULL;
	(E)BX = (E)AX;
	(E)BX--;

LENGTH_CHECK_LOOP:
	(E)BX++;

	IF (!DSBYTE[(E)BX])
           GOTO ASL_END;
	IF (DSBYTE[(E)BX] == CL)
           GOTO ASL_ENDING;

        GOTO LENGTH_CHECK_LOOP;

ASL_ENDING:
	DX++;

ASL_END:
	(E)BX -= (E)AX;
? ifdef codesize
	(E)AX >< (E)BX;
? else
	(E)AX = (E)BX;
? endif
}



inline byte ADVANCEDSTRCMP ((E)SI, (E)BX, CL)   // ver. 1.03, user specified
						//	    terminator
/* Compares two strings.
   Basically STRCMP() but accepts also user specified character as terminator
     and doesn't determine if string1 is greater/smaller than string2
   Input:   DS:SI = string2
	    DS:BX = string1
	       CL = other terminating character besides 0
   Output:  AL = 0 if the strings are equal
	    AL = 1 if strings not equal
	    CL, BX, SI destroyed
   Uses:    LOAD_AL() from BYTES.H--
   Size:    min. 40 bytes
*/
{
AH = EMPTY;
(E)BX--;

TOP:
(E)BX++;
@LOAD_AL();
IF (AL <> DSBYTE[(E)BX])
   GOTO CHECK_TERMINATOR;

IF (!AL) || (AL == CL)
   GOTO HERE;
GOTO TOP;

CHECK_TERMINATOR:
IF (!AL)
   GOTO CHECK_BX;
IF (AL <> CL)
   GOTO CHANGE_AH;

CHECK_BX:
IF (!DSBYTE[(E)BX]) || (DSBYTE[(E)BX] == CL)
   GOTO HERE;

CHANGE_AH:
AH++;

HERE:
AH >< AL;
}



inline word STRINGLEN (DI)
/* Determines the length of a 0 terminating string
   Basically STRLEN() in STRING.H-- but uses DOS 3.0+ internal code
   Note:   !!!   not supported by all DOS-clones   !!!
   Input:  ES:DI = ASCIZ string
   Output: AX = length of string, not including 0 terminator
	   CX destroyed
   Uses:   0 - 2 bytes of stack
	   AX2CX() from BYTES.H--
   Size:   min. 10 bytes
*/
{
? DOSrequired 0x0300

AX = 0x1212;
$INT 0x2F
CX--;

@AX2CX();
}



inline void STRINGUP (SI)
/* Converts a 0 terminating string to uppercase
   Basically STR_UP() in STRING.H-- but uses DOS 3.0+ internal code.
   Notes:  DS must equal to ES
	   !!!  '/' in string are changed to '\'   !!!
	   !!!   not supported by all DOS-clones   !!!
   Input:  DS:SI = ASCIZ string
   Output: nothing
	   AX destroyed
	   DI = SI
   Size:   min. 10 bytes
*/
{
? DOSrequired 0x0300

DI = SI;
AX = 0x1211;
$INT 0x2F
}



inline word STRINGCMP (SI, DI)
/* Compares two 0 terminated strings
   Basically STRCMP() in STRING.H-- but uses DOS 3.0+ internal code.
   Note:   DS must equal to ES
	   !!!   not supported by all DOS-clones   !!!
   Input:  DS:SI = first ASCIZ string
	   ES:DI = second ASCIZ string
   Output: AX = 0 if strings are equal
	   AX = 1 if not equal
	   BX, SI, DI undetermined
   Uses:   AX2BX() from BYTES.H--
   Size:   ca. 17 bytes
*/
{
? DOSrequired 0x0300

AX = 0x121E;
BX = 0;
$INT 0x2F

IF (NOTZEROFLAG)
   BX++;

@AX2BX();
}



inline word ASCIZ2WORDFAR (BX, (E)SI, CL)
/* Converts ASCIZ strings to numbers.
   Like ASCIZ2WORD() but handles a different segment from CS
   Input:  BX:SI = string
	      CL = base for conversion
	   'insertASCIZ2WORD' definied to insert not call ASCIZ2WORD()
	   'InsertCHARMULTIPLY' definied to insert not call CHARMULTIPLY()
   Output: AX = 0 if conversion successful
	     DX = number
	   AX <> 0 by error (if number over FFFFh or no number at all)
	     DX undetermined
	   BX, CX, SI
   Uses:   ASCIZ2WORD() from STRING2.H--
           2 - 8 bytes of stack
   Size:   min. 86 bytes
*/
{
$PUSH DS
DS = BX;
? ifdef insertASCIZ2WORD
  @
? endif
ASCIZ2WORD((E)SI,CL);
$POP DS
}



inline word STR2WORDFAR (BX, (E)SI, CL, (E)DI)
/* Converts strings to numbers.
   Like STR2WORD() but handles a different segment from CS
   Input:  BX:SI = string
	      CL = base for conversion
	      DI = length of string
	   'insertSTR2WORD' definied to insert not call STR2WORD()
	   'InsertCHARMULTIPLY' definied to insert not call CHARMULTIPLY()
   Output: AX = 0 if conversion successful
	     DX = number
	   AX <> 0 by error (if number over FFFFh or no number at all)
	     DX undetermined
	   BX, CX, SI
   Uses:   STR2WORD() from STRING2.H--
           2 - 8 bytes of stack
   Size:   min. 85 bytes
*/
{
$PUSH DS
DS = BX;
? ifdef insertSTR2WORD
  @
? endif
STR2WORD((E)SI,CL,(E)DI);
$POP DS
}



: byte STRINGCUT ((E)SI, BL, DH, DL, (E)CX)   // ver. 1.02, not limited
					      //   to *after char*
/* Terminates ASCIZ string by the first (or last) occourence of given character
   Notes:  the rest of string is not deleted, just the character after the
	     specified char is overwritten with a zero
	   only ASCIZ (zero-terminatied) strings are supported
	   length in CX is for cases when the new string has to fit into a
	     buffer with limited length. In this case the scan is terminated
	     and error returned if the string is longer than requested.
   Input:  DS:SI = address of string
	      BL = character by which to handle
	      DH = start of scanning => STRINGSTART (0) - at the beginning
					  STRINGEND (1) - at the end
	      DL = where to "cut" => AFTERCHAR (0) - after given char
					ATCHAR (1) - at char (ie zero the
						     character itself)
				    BEFORECHAR (2) - before then character
	      CX = maximum length of new string =>
		   number or 0 (unlimited length)
   Output: AL = 0 if cutting successful
	     AH = byte which was overwritten
	     CX = length of string after cutting (not including terminator)
	   AL <> 0 if couldn't be cut
	     AL = TOO_LONG (1) if new string too long
	     AL = NO_CHAR (2) if char wasn't found
	     AH = 0
	     CX undefinied
	   BP preserved
	   BX, DX, SI, DI destroyed
   Uses: 4 bytes of stack
   Size: ca. 102 bytes
*/
{
$PUSHF
$PUSH (E)BP
AX = NULL;
(E)DI = (E)BP = (E)SI;
$CLD
IF (!DSBYTE[(E)SI])
   GOTO SET_ERR1;
do (E)SI++;
   while (DSBYTE[(E)SI]);
IF (DH)
   {
   (E)SI += TWO;
   (E)DI--;
   $STD
   }

$JCXZ CHECK_BH;
(E)CX += (E)DI;

CHECK_BH:
IF (!DH)
   (E)SI >< (E)DI;
do {
   $LODSB
   IF (AL == BL)
      {
      IF (DH)
	 {
	 $CLD
	 IF (DL == BEFORECHAR)
            GOTO CHAR_ERASE;
	 IF (DL == AFTERCHAR)
	    (E)SI++;
	 (E)SI++;
	 }
      ELSE
	 {
	 IF (!DL)
            GOTO CHAR_ERASE;
	 IF (DL == BEFORECHAR)
	    (E)SI--;
	 (E)SI--;
	 }
     CHAR_ERASE:
      (E)CX = (E)SI - (E)BP;
      $LODSB
      DSBYTE[(E)SI - ONE] = NULL;
      GOTO END_PROC;
      }
   $JCXZ CONTINUE_LOOP;
   IF ((E)SI == (E)CX))
      GOTO SET_ERR2;
  CONTINUE_LOOP:
   } while ((E)SI <> (E)DI);
SET_ERR1:
AH++;
SET_ERR2:
AH++;
END_PROC:
AH >< AL;
$POP (E)BP
$POPF
}



inline void ASCIZ2DOS ((E)BX)   // ver. 1.01, checks first byte
/* Converts ASCIZ (zero-terminated) string to DOS ('$'-terminated) string
   Input:  DS:BX = ASCIZ string
   Output: nothing
	   BX destroyed
   Size: 11 bytes
*/
{
WHILE (DSBYTE[(E)BX])
   (E)BX++;
DSBYTE[(E)BX] = '$';
}



inline void DOS2ASCIZ ((E)BX)   // ver. 1.01, bugfixed + checks first byte
/* Converts DOS ('$'-terminated) string to ASCIZ (zero-terminated) string
   Input:  DS:BX = DOS string
   Output: nothing
	   BX destroyed
   Size: 11 bytes
*/
{
WHILE (DSBYTE[(E)BX] <> '$')
   (E)BX++;
DSBYTE[(E)BX] = NULL;
}



inline void ADVANCEDSTRUP ((E)BX, CL)   // 20031005, returns type of terminat.
/* Converts all lower case (standard English) letters in a string to uppercase
   Differs from standard STR_UP() by accepting also user input as terminator
   Input:  DS:BX = string
	      CL = additional terminator besides 0
   Output: nothing
	   DX = type of terminator ==> 0 - '0'
				       1 - user specified
	   AX destroyed
   Uses:   0 - 2 bytes of stack
   Size:   min. 28 bytes
*/
{
DX = NULL;

THE_LOOP:
AL = DSBYTE[(E)BX];
IF (AL == CL)
   {
   DX++;
   GOTO END_VOID;
   }

IF (AL >= 'a') && (AL <= 'z')
   DSBYTE[(E)BX] = AL & 0x5F;
(E)BX++;
IF (AL)
   GOTO THE_LOOP;

END_VOID:
}



inline void ADVANCEDSTRDOWN ((E)BX, CL)   // 20031005, returns type of termin.
/* Converts all lower case (standard English) letters in a string to lowercase
   Differs from standard STR_DOWN() by accepting user input also as terminator
   Input:  DS:BX = string
	      CL = additional terminator besides 0
   Output: nothing
	   DX = type of terminator ==> 0 - '0'
				       1 - user specified
	   AX destroyed
   Size: min. 25 bytes
*/
{
DX = NULL;

THE_LOOP:
AL = DSBYTE[(E)BX];
IF (AL == CL)
   {
   DX++;
   GOTO END_VOID;
   }

IF (AL <= 'Z') && (AL >= 'A')
   DSBYTE[(E)BX] = AL | 0x20;
(E)BX++;

IF (AL)
   GOTO THE_LOOP;

END_VOID:
}



inline void STRCPYNEAR ((E)DI, (E)SI)
/* Copies an ASCIZ (zero-terminated) string
   Basically STRCPY() from STRING.H-- but doesn't set ES
   Input:  ES:DI = destination
	   DS:SI = source
   Output: nothing
	   SI, DI destroyed
   Uses:   2 - 4 bytes of stack
	   LOAD_AL() and STORE_AL() from BYTES.H--
   Size:   min. 9 bytes
*/
{
$PUSHF
$CLD
do {
   @LOAD_AL();
   @STORE_AL();
   } while (AL);
$POPF
}



inline void STRCPYFAR (AX, DI, BX, SI)
/* Copies an ASCIZ (zero-terminated) string
   Basically STRCPYNEAR() but sets also DS and ES
   Input:  AX:DI = destiny
	   BX:SI = source
	   'insertSTRCPYNEAR' definied to insert not call STRCPYNEAR()
   Output: nothing
	   AX, BX, SI, DI destroyed
   Uses:   6 - 10 bytes of stack
	   STRCPYNEAR() from STRING2.H--
	   LOAD_AL() and STORE_AL() from BYTES.H--
   Size:   min. 17 bytes
*/
{
$PUSH DS,ES
ES = AX;
DS = BX;
? ifdef insertSTRCPYNEAR
  @
? endif
STRCPYNEAR();
$POP ES,DS
}



inline void STRCPYTOFAR (AX, DI, SI)
/* Copies an ASCIZ (zero-terminated) string from near to far memory
   Basically STRCPYFAR() but sets only ES
   Input:  AX:DI = destination
	   DS:SI = source
	   'insertSTRCPYNEAR' definied to insert not call STRCPYNEAR()
   Output: nothing
	   AX, SI, DI destroyed
   Uses:   4 - 8 bytes of stack
	   STRCPYNEAR() from STRING.H--
	   LOAD_AL() and STORE_AL() from BYTES.H--
   Size:   min. 13 bytes
*/
{
$PUSH ES
ES = AX;
? ifdef insertSTRCPYNEAR
  @
? endif
STRCPYNEAR();
$POP ES
}



inline void STRCPYFROMFAR (DI, BX, SI)
/* Copies an ASCIZ (zero-terminated) string from far to near memory
   Basically STRCPYFAR() but sets only DS
   Input:  ES:DI = destination
	   BX:SI = source
	   'insertSTRCPYNEAR' definied to insert not call STRCPYNEAR()
   Output: nothing
	   AX, SI, DI destroyed
   Uses:   4 - 8 bytes of stack
	   STRCPYNEAR() from STRING.H--
	   LOAD_AL() and STORE_AL() from BYTES.H--
   Size:   min. 13 bytes
*/
{
$PUSH DS
DS = BX;
? ifdef insertSTRCPYNEAR
  @
? endif
STRCPYNEAR();
$POP DS
}



inline void ADVANCEDSTRCPYNEAR ((E)DI, (E)SI, CL)
/* Copies a string
   Basically STRCPY() from STRING.H-- but accepts also user-specified
     character as terminator and doesn't set ES
   Input:  ES:DI = destination
	   DS:SI = source
	      CL = additionally supported terminator besides '0'
   Output: nothing
	   DX = type of terminator ==> 0 - '0'
				       1 - user specified
	   AL, SI, DI destroyed
   Uses:   2 - 4 bytes of stack
	   LOAD_AL() and STORE_AL() from BYTES.H--
   Size:   min. 18 bytes
*/
{
$PUSHF
$CLD

DX = NULL;

THE_LOOP:
IF (@LOAD_AL() == CL)
   {
   DX++;
   AL = NULL;
   }
@STORE_AL();
IF (AL)
   GOTO THE_LOOP;

$POPF
}



inline void ADVANCEDSTRCPYFAR (AX, (E)DI, BX, (E)SI, CL)
/* Copies a string
   Basically ADVANCEDSTRCPYNEAR() but sets also source and destiny segments
   Input:  AX:DI = destination
	   BX:SI = source
	      CL = additionally supported terminator besides '0'
	   'insertADVANCEDSTRCPYNEAR'' definied to insert ADVANCEDSTRCPYNEAR()
	      instead of calling
   Output: nothing
	   DX = type of terminator ==> 0 - '0'
				       1 - user specified
	   AX, BX, SI, DI destroyed
   Uses:   6 - 10 bytes of stack
	   ADVANCEDSTRCPYNEAR() from STRING2.H--
	   LOAD_AL() and STORE_AL() from BYTES.H--
   Size:   min. 26 bytes
*/
{
$PUSH DS,ES
ES = AX;
DS = BX;

? ifdef insertADVANCEDSTRCPYNEAR
  @
? endif
ADVANCEDSTRCPYNEAR();

$POP ES,DS
}



inline void ADVANCEDSTRCPYTOFAR (AX, (E)DI, (E)SI, CL)
/* Copies a string from near to far memory.
   Basically ADVANCEDSTRCPYFAR() but sets only destiny segment.
   Input:  AX:DI = destination
	   DS:SI = source
	      CL = additionally supported terminator besides '0'
	   'insertADVANCEDSTRCPYNEAR'' definied to insert ADVANCEDSTRCPYNEAR()
	      instead of calling
   Output: nothing
	   DX = type of terminator ==> 0 - '0'
				       1 - user specified
	   AX, BX, SI, DI destroyed
   Uses:   6 - 10 bytes of stack
	   ADVANCEDSTRCPYNEAR() from STRING2.H--
	   LOAD_AL() and STORE_AL() from BYTES.H--
   Size:   min. 22 bytes
*/
{
$PUSH ES
ES = AX;

? ifdef insertADVANCEDSTRCPYNEAR
  @
? endif
ADVANCEDSTRCPYNEAR();

$POP ES
}



inline void ADVANCEDSTRCPYFROMFAR ((E)DI, BX, (E)SI, CL)
/* Copies a string from far to near memory
   Basically ADVANCEDSTRCPYFAR() but sets only source segment
   Input:  ES:DI = destination
	   BX:SI = source
	      CL = additionally supported terminator besides '0'
	   'insertADVANCEDSTRCPYNEAR'' definied to insert ADVANCEDSTRCPYNEAR()
	      instead of calling
   Output: nothing
	   DX = type of terminator ==> 0 - '0'
				       1 - user specified
	   AX, SI, DI destroyed
   Uses:   6 - 10 bytes of stack
	   ADVANCEDSTRCPYNEAR() from STRING2.H--
	   LOAD_AL() and STORE_AL() from BYTES.H--
   Size:   min. 22 bytes
*/
{
$PUSH DS
DS = BX;

? ifdef insertADVANCEDSTRCPYNEAR
  @
? endif
ADVANCEDSTRCPYNEAR();

$POP DS
}



inline word ASCIZ2WORD ((E)SI, CL)   // 20040705
/* Converts ASCIZ strings (like "1234") to numbers (1234 or 1234h)
   Note:   only WORD values are supported (ie max. converted number is 65535)
   Input:  DS:SI = string
	      CL = base for conversion (10 for decimal, 16 for hex, etc.)
	   'insertCHARMULTIPLY' definied to insert not call CHARMULTIPLY()
   Output: AX = 0 if conversion successful
	     DX = number
	   AX <> 0 if error accoured (no number or num too big)
	     DX undetermined
	   BX, CX, SI destroyed
   Uses:   CHARMULTIPLY() from STRING2.H--
	   0 - 4 bytes of stack
   Size:   min. 81 bytes
*/
{
BH = CH = AX = NULL;

// Take care that an empty string would trigger error
DL = 0xFF;

WHILE (DSBYTE[(E)SI])
   {
   ? ifdef insertCHARMULTIPLY
     @
   ? endif
   CHARMULTIPLY();
   IF (DX)
      GOTO END_PROC;
   }
END_PROC:
AX >< DX;
}



inline word STR2WORD ((E)SI, CL, (E)DI)   // 20040705
/* Converts strings with specified lenght to numbers.
   Note:   only WORD values are supported (ie max. converted number is 65535)
   Input:  DS:SI = string
	      CL = base for conversion (10 for decimal, 16 for hex, etc.)
	      DI = length of string
	   'insertCHARMULTIPLY' definied to insert not call CHARMULTIPLY()
   Output: AX = 0 if conversion successful
	     DX = number
	   AX <> 0 if error accoured (no number or num too big)
	     DX undetermined
	   BX, CX, SI, DI destroyed
   Uses:   CHARMULTIPLY() from STRING2.H--
	   0 - 4 bytes of stack
   Size:   min. 82 bytes
*/
{
BH = CH = AX = NULL;
(E)DI += (E)SI;

// Take care that an empty string would trigger error
DL = 0xFF;

WHILE ((E)SI < (E)DI)
   {
   ? ifdef insertCHARMULTIPLY
     @
   ? endif
   CHARMULTIPLY();
   IF (DX)
      GOTO END_PROC;
   }
END_PROC:
AX >< DX;
}



: word CHARMULTIPLY ((E)SI, CL)   // 20040705
/* Multiplies characters.
   Used internally by ASCIZ2WORD() and STR2WORD()
   Note:   maximum supported base is 26
   Input:  DS:SI = string
	      CL = base for conversion (10 for decimal, 16 for hex, etc.)
   Output: AX = number
	   DX = 0 if numbver successfully multiplied
	   DX <> 0 if error occoured (no number or num too big)
	   BX, CX, SI destroyed
   Uses:   0 - 2 bytes of stack
   Size:   min. 61 bytes
*/
{
BL = DSBYTE[(E)SI];

IF (BL >= '0') && (BL <= '9')
   BL -= '0';
ELSE IF (BL >= 'A') && (BL <= 'Z')
   BL = BL - 'A' + 10;
ELSE IF (BL >= 'a') && (BL <= 'z')
   BL = BL - 'a' + 10;

IF (BL > CL)
   GOTO RETURN_ERROR;

$MUL CX
AX += BX;
IF (CARRYFLAG) || (DX)
   {
  RETURN_ERROR:
   DX++;
   }
SI++;
}


? endif


/* end STRING2.H-- */
