U8 *StrPrintHex(U8 *dst, I64 num; I64 width)
{
	U8 *res = dst + width;

	dst = res;
	while (width--)
	{
		*--dst = "0123456789ABCDEF"(U8 *)[num & 15];
		num >>= 4;
	}

	return res;
}

U0 PutHex(I64 num, I64 width)
{
	U8 buf[17];

	if (width > 16)
		width = 16;

	*StrPrintHex(buf, num, width) = 0;
	"%s", buf;
}

asm {
// IN:	RAX=NUM TO PRINT
PUT_HEX_U64::
				PUSH_C_REGS
				PUSH		16
				PUSH		RAX
				CALL		&PutHex
				POP_C_REGS
				RET
PUT_HEX_U32::
				PUSH_C_REGS
				PUSH		8
				PUSH		RAX
				CALL		&PutHex
				POP_C_REGS
				RET
PUT_HEX_U16::
				PUSH_C_REGS
				PUSH		4
				PUSH		RAX
				CALL		&PutHex
				POP_C_REGS
				RET
PUT_HEX_U8::
				PUSH_C_REGS
				PUSH		2
				PUSH		RAX
				CALL		&PutHex
				POP_C_REGS
				RET
PUT_CHARS::
// IN:	RAX=Char
				PUSH_C_REGS
				PUSH		RAX
				CALL		&PutChars
				POP_C_REGS
				RET
PUT_STR::
// IN:	RSI=String
				PUSH_C_REGS
				PUSH		RSI
				CALL		&PutS
				POP_C_REGS
				RET
_STRCOPY::
				PUSH		RBP
				MOV 		RBP, RSP
				PUSH		RSI
				PUSH		RDI
				MOV 		RDI, U64 SF_ARG1[RBP]
				TEST		RDI, RDI
				JZ			@@15
				MOV 		RSI, U64 SF_ARG2[RBP]
				TEST		RSI, RSI
				JNZ 		@@05
				XOR 		RAX, RAX
				JMP 		@@10
@@05:			LODSB
@@10:			STOSB
				TEST		AL,  AL
				JNZ 		@@05
@@15:			POP 		RDI
				POP 		RSI
				POP 		RBP
				RET1		16
_STRCOMPARE::
				PUSH		RBP
				MOV 		RBP, RSP
				PUSH		RSI
				PUSH		RDI
				MOV 		RSI, U64 SF_ARG2[RBP]
				MOV 		RDI, U64 SF_ARG1[RBP]
@@05:			LODSB
				TEST		AL,  AL
				JZ			@@20
				SCASB
				JE			@@05
				JA			@@15
@@10:			MOV 		RAX, 1
				JMP 		@@25
@@15:			MOV 		RAX, -1
				JMP 		@@25
@@20:			SCASB
				JNE 		@@10
				XOR 		RAX, RAX
@@25:			POP 		RDI
				POP 		RSI
				POP 		RBP
				RET1		16
TO_UPPER::
				CMP 		AL,  'a'
				JB			@@05
				CMP 		AL,  'z'
				JA			@@05
				ADD 		AL,  'A' - 'a'
@@05:			RET
_STRICOMPARE::
				PUSH		RBP
				MOV 		RBP, RSP
				PUSH		RSI
				PUSH		RDI
				MOV 		RSI, U64 SF_ARG2[RBP]
				MOV 		RDI, U64 SF_ARG1[RBP]
@@05:			LODSB
				TEST		AL,  AL
				JZ			@@30
				CMP 		AL,  'a'
				JB			@@10
				CMP 		AL,  'z'
				JA			@@10
				ADD 		AL,  'A' - 'a'
@@10:			MOV 		BL,  U8 [RDI]
				INC 		RDI
				CMP 		BL,  'a'
				JB			@@15
				CMP 		BL,  'z'
				JA			@@15
				ADD 		BL,  'A' - 'a'
@@15:			CMP 		AL,  BL
				JE			@@05
				JA			@@25
@@20:			MOV 		RAX, 1
				JMP 		@@35
@@25:			MOV 		RAX, -1
				JMP 		@@35
@@30:			MOV 		BL,  U8 [RDI]
				TEST		BL,  BL
				JNE 		@@20
				XOR 		RAX, RAX
@@35:			POP 		RDI
				POP 		RSI
				POP 		RBP
				RET1		16
_STRNCOMPARE::
				PUSH		RBP
				MOV 		RBP, RSP
				PUSH		RSI
				PUSH		RDI
				MOV 		RCX, U64 SF_ARG3[RBP]
				MOV 		RSI, U64 SF_ARG2[RBP]
				MOV 		RDI, U64 SF_ARG1[RBP]
@@05:			TEST		RCX, RCX
				JZ			@@25
				DEC 		RCX
				LODSB
				TEST		AL,  AL
				JZ			@@20
				SCASB
				JE			@@05
				JA			@@15
@@10:			MOV 		RAX, 1
				JMP 		@@30
@@15:			MOV 		RAX, -1
				JMP 		@@30
@@20:			MOV 		BL,  U8 [RDI]
				TEST		BL,  BL
				JNE 		@@10
@@25:			XOR 		RAX, RAX
@@30:			POP 		RDI
				POP 		RSI
				POP 		RBP
				RET1		24
_STRNICOMPARE::
				PUSH		RBP
				MOV 		RBP, RSP
				PUSH		RSI
				PUSH		RDI
				MOV 		RCX, U64 SF_ARG3[RBP]
				MOV 		RSI, U64 SF_ARG2[RBP]
				MOV 		RDI, U64 SF_ARG1[RBP]
@@05:			TEST		RCX, RCX
				JZ			@@35
				DEC 		RCX
				LODSB
				TEST		AL,  AL
				JZ			@@30
				CMP 		AL,  'a'
				JB			@@10
				CMP 		AL,  'z'
				JA			@@10
				ADD 		AL,  'A' - 'a'
@@10:			MOV 		BL, U8 [RDI]
				INC 		RDI
				CMP 		BL,  'a'
				JB			@@15
				CMP 		BL,  'z'
				JA			@@15
				ADD 		BL,  'A' - 'a'
@@15:			CMP 		AL,  BL
				JE			@@05
				JA			@@25
@@20:			MOV 		RAX, 1
				JMP 		@@40
@@25:			MOV 		RAX, -1
				JMP 		@@40
@@30:			SCASB
				JNE 		@@20
@@35:			XOR 		RAX, RAX
@@40:			POP 		RDI
				POP 		RSI
				POP 		RBP
				RET1		24
_STRMATCH::
				PUSH		RBP
				MOV 		RBP, RSP
				PUSH		RSI
				PUSH		RDI
				MOV 		RSI, U64 SF_ARG2[RBP]
				TEST		RSI, RSI
				JZ			@@25
				MOV 		RDI, U64 SF_ARG1[RBP]
				TEST		RDI, RDI
				JZ			@@25
				MOV 		DL,  U8 [RDI]
				TEST		DL,  DL
				JZ			@@20
				JMP 		@@10
@@05:			INC 		RSI
@@10:			LODSB
				TEST		AL,  AL
				JZ			@@25
				CMP 		AL,  DL
				JNE 		@@10
				DEC 		RSI
				MOV 		RCX, 1
@@15:			MOV 		AL,  U8 [RDI + RCX]
				TEST		AL,  AL
				JZ			@@20
				CMP 		AL,  U8 [RSI + RCX]
				JNE 		@@05
				INC 		RCX
				JMP 		@@15

				DEC 		RSI
@@20:			MOV 		RAX, RSI
				JMP 		@@30
@@25:			XOR 		RAX, RAX
@@30:			POP 		RDI
				POP 		RSI
				POP 		RBP
				RET1		16
_STRIMATCH::
				PUSH		RBP
				MOV 		RBP, RSP
				PUSH		RSI
				PUSH		RDI
				MOV 		RSI, U64 SF_ARG2[RBP]
				TEST		RSI, RSI
				JZ			@@25
				MOV 		RDI, U64 SF_ARG1[RBP]
				TEST		RDI, RDI
				JZ			@@25
				MOV 		AL,  U8 [RDI]
				CALL		TO_UPPER
				MOV 		DL,  AL
				TEST		DL,  DL
				JZ			@@20
				JMP 		@@10
@@05:			INC 		RSI
@@10:			LODSB
				CALL		TO_UPPER
				TEST		AL,  AL
				JZ			@@25
				CMP 		AL,  DL
				JNE 		@@10
				DEC 		RSI
				MOV 		RCX, 1
@@15:			MOV 		AL,  U8 [RDI + RCX]
				CALL		TO_UPPER
				TEST		AL,  AL
				JZ			@@20
				MOV 		BL,  U8 [RSI + RCX]
				XCHG		AL,  BL
				CALL		TO_UPPER
				CMP 		AL,  BL
				JNE 		@@05
				INC 		RCX
				JMP 		@@15

				DEC 		RSI
@@20:			MOV 		RAX, RSI
				JMP 		@@30
@@25:			XOR 		RAX, RAX
@@30:			POP 		RDI
				POP 		RSI
				POP 		RBP
				RET1		16
}
_extern _STRCOMPARE		I64 StrCompare(U8 *st1,   U8 *st2); //Compare two strings.
_extern _STRICOMPARE	I64 StrICompare(U8 *st1,  U8 *st2); //Compare two strings,  ignoring case.
_extern _STRNCOMPARE	I64 StrNCompare(U8 *st1,  U8 *st2, I64 n); //Compare N bytes in two strings.
_extern _STRNICOMPARE	I64 StrNICompare(U8 *st1, U8 *st2, I64 n); //Compare N bytes in two strings, ignoring case.
_extern _STRMATCH		U8 *StrMatch(U8 *needle,  U8 *haystack_str);//Scan for string in string.
_extern _STRIMATCH		U8 *StrIMatch(U8 *needle, U8 *haystack_str);//Scan for string in string, ignoring case.
_extern _STRCOPY		U0  StrCopy(U8 *dst, U8 *src); //Copy string.

//These bitmaps go to 0-511 so that $LK,"Lex",A="MN:Lex"$() can use them with $LK,"Token Codes",A="MN:TK_EOF"$.
U32
	char_bmp_alpha[16] =
	{0x0000000, 0x00000000, 0x87FFFFFF, 0x07FFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_alpha_numeric[16] =
	{0x0000000, 0x03FF0000, 0x87FFFFFF, 0x07FFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_alpha_numeric_no_at[16] =
	{0x0000000, 0x03FF0000, 0x87FFFFFE, 0x07FFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_word[16] =
	{0x0000000, 0x03FF0080, 0x87FFFFFE, 0x07FFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_filename[16] =
	{0x0000000, 0x03FF73FB, 0xEFFFFFFF, 0x6FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_dec_numeric[16] =
	{0x0000000, 0x03FF0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_hex_numeric[16] =
	{0x0000000, 0x03FF0000, 0x7E, 0x7E, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_white_space[16] =
	{0x80002600, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_non_eol_white_space[16] =
	{0x80000200, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_zero_cr_nl_cursor[16] =
	{0x00002421, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_zero_tab_cr_nl_cursor[16] =
	{0x00002621, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_zero_tab_cr_nl_cursor_dollar[16] =
	{0x00002621, 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_macro[16] =
	{0x80002600, 0xFFFFFFDF, 0xFFFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_printable[16] =
	{0x80002600, 0xFFFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_displayable[16] =
	{0x80000000, 0xFFFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, 0}, 

	char_bmp_safe_dollar[16] =
	{0x80000000, 0xFFFFFFEF, 0xFFFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, 0},
	//same but no dollar sign

	char_bmp_non_eol[16] =
	{0xFFFFDBFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 
	 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};

U8 *ListSub(I64 sub,  U8 *list)
{//Point to list entry.
//Not efficient.  Use an array of U8 ptrs for efficiency.
	if (!list)
		return NULL;
	while (*list && sub > 0)
	{
		while (*list) 			//Advance to end of cur entry.
			list++;
		list++; 						//Skip trailing zero.
		if (*list == '@') 		//Check for '@' alias list entry.
			list++;
		else
			sub--;
	}
	if (sub || !*list)
		return NULL;
	else
		return list;
}

I64 ListMatch(U8 *needle, U8 *haystack_list, I64 flags=0)
{//-2 if Ambiguous
// -1 if not found
	// Not efficient. Use hash tables for efficiency.
	I64	 n, sub = 0, res = -1;
	U8	*ptr;
	Bool exact_match = FALSE;

	if (!haystack_list)
		return -1;

	n = StrLen(needle);
	while (*haystack_list)
	{
		if (*haystack_list == '@')
		{	//Check for '@' alias haystack_list entry
			sub--;
			haystack_list++;
		}
		ptr = needle;
		if (flags & LMF_IGNORE_CASE)
			while (*ptr && ToUpper(*ptr) == ToUpper(*haystack_list))
			{
				ptr++;
				haystack_list++;
			}
		else
			while (*ptr && *ptr == *haystack_list)
			{
				ptr++;
				haystack_list++;
			}
		if (!*ptr)
		{							//Did we reach end of needle?
			if (!*haystack_list)	//Did we reach end of haystack_list?
				return sub; 		//Found Exact match
			else
			{
				if (res != -1)
				{
					if (!exact_match)
						res = -2; 	//Ambiguous unless later exact match.
				}
				else
				{
					if (!(flags & LMF_EXACT))
						res = sub;
				}
			}
		}
		while (*haystack_list)			//Advance to end of cur entry.
			haystack_list++;
		haystack_list++;				//Skip trailing zero
		sub++;
	}

	return res;
}

I64 StrOcc(U8 *src, I64 ch)
{//Count occurrences of a char.
	I64 i = 0;

	if (!src)
		return 0;
	while (*src)
		if (*src++ == ch)
			i++;

	return i;
}

I64 Spaces2Tabs(U8 *dst, U8 *src)
{//Src buf with spaces to dst buf without.
	U8 *src2;
	I64 chged = 0, space_count, space_count2, col = 0;

	if (*src)
		while (TRUE)
		{
			src2 = src;
			while (*src2 == CH_SPACE)
				src2++;
			space_count = src2 - src;
			while (col + space_count >= 4)
			{
				space_count2 = 4 - col;
				if (space_count2 == 1)
					*dst++ = CH_SPACE;
				else
				{
					*dst++ = '\t';
					chged += space_count2 - 1;
				}
				space_count -= space_count2;
				col = 0;
			}
			if (*src2 == '\t')
			{
				if (space_count == 1 && col == 3)
					*dst++ = CH_SPACE;
				else
					chged += space_count;
				*dst++ = '\t';
				col = 0;
			}
			else
			{
				while (space_count--)
				{
					*dst++ = CH_SPACE;
					if (++col == 4)
						col = 0;
				}
				if (*src2)
				{
					*dst++ = *src2;
					if (++col == 4)
						col = 0;
				}
				else
					break;
			}
			src = ++src2;
		}
	*dst = 0;

	return chged;
}

U8 *StrUtil(U8 *_src, I64 flags)
{//Modifies in place. See $LK,"flags",A="MN:SUF_REM_SPACES"$ for all the options.
	U8 *src = _src, *dst = _src;
	I64 ch;

	if (flags & SUF_REM_LEADING)
		while (Bt(char_bmp_white_space, *src))
			src++;
	while (ch = *src++)
		if (Bt(char_bmp_white_space, ch))
		{
			if (!(flags & SUF_REM_SPACES))
			{
				if (flags & SUF_SINGLE_SPACE)
				{
					*dst++ = CH_SPACE;
					while ((ch = *src++) && Bt(char_bmp_white_space, ch));
					src--;
				}
				else
					*dst++ = ch;
			}
		}
		else if (!(flags & SUF_REM_CTRL_CHARS) || ch >= CH_SPACE)
			*dst++ = ch;
	*dst = 0;

	if (flags & SUF_REM_TRAILING)
		while (dst != _src && (!*dst || Bt(char_bmp_white_space, *dst)))
			*dst-- = 0;
	if (flags & SUF_TO_UPPER)
		for (dst = _src; *dst; dst++)
		{
			ch = *dst;
			if ('a' <= ch <= 'z')
				*dst = ch - 0x20;
		}
	if (flags & SUF_TO_LOWER)
		for (dst = _src; *dst; dst++)
		{
			ch = *dst;
			if ('A' <= ch <= 'Z')
				*dst = ch + 0x20;
		}
	if (flags & SUF_SAFE_DOLLAR)
		for (dst = _src; *dst; dst++)
		{
			ch = *dst;
			if (!Bt(char_bmp_safe_dollar, *dst))
				*dst = '.';
		}
	if (flags & SUF_S2T)
		Spaces2Tabs(_src, _src);

	return _src;
}

U8 *StrFirstOcc(U8 *src, U8 *marker)
{//Point to 1st occurrence of marker set in str.
	I64 ch;

	while ((ch = *src++) && !StrOcc(marker, ch));
	if (ch)
		return src - 1;
	else
		return NULL;
}

U8 *StrFirstRemove(U8 *src, U8 *marker, U8 *dst=NULL)
{//Remove first str segment and place in dst buf or NULL.
	I64 ch;
	U8 *ptr = src, *res = dst;

	if (dst)
	{
		while ((ch = *ptr++) && !StrOcc(marker, ch))
			*dst++ = ch;
		*dst = 0;
	}
	else
		while ((ch = *ptr++) && !StrOcc(marker, ch));
	if (ch)
		StrCopy(src, ptr);
	else
		*src = 0;

	return res;
}

U8 *StrLastOcc(U8 *src, U8 *marker)
{//Point to last occurrence of market set in str.
	I64 ch;
	U8 *res = NULL;

	while (ch = *src++)
		if (StrOcc(marker, ch))
			res = src - 1;

	return res;
}

U8 *StrLastRemove(U8 *src, U8 *marker, U8 *dst=NULL)
{//Remove last str segment and place in dst buf or NULL.
	U8 *ptr;

	if (ptr = StrLastOcc(src, marker))
	{
		if (dst)
			StrCopy(dst, ptr + 1);
		*ptr = 0;
	}
	else
	{
		if (dst)
			StrCopy(dst, src);
		*src = 0;
	}

	return dst;
}

U8 *StrFind(U8 *needle, U8 *haystack_str, I64 flags=0)
{//Find needle_str in haystack_str with options.
	Bool cont;
	U8	*saved_haystack_str = haystack_str;
	I64	 plen = StrLen(needle);

	do
	{
		cont = FALSE;
		if (flags & SFF_IGNORE_CASE)
			haystack_str = StrIMatch(needle, haystack_str);
		else
			haystack_str = StrMatch(needle, haystack_str);
		if (haystack_str && flags & SFF_WHOLE_LABELS_BEFORE && haystack_str != saved_haystack_str &&
			Bt(char_bmp_alpha_numeric, *(haystack_str - 1)))
		{
			haystack_str++;
			if (*haystack_str)
				cont = TRUE;
			else
				haystack_str = NULL;
		}
		if (haystack_str && flags & SFF_WHOLE_LABELS_AFTER && Bt(char_bmp_alpha_numeric, *(haystack_str + plen)))
		{
			haystack_str++;
			if (*haystack_str)
				cont = TRUE;
			else
				haystack_str = NULL;
		}
	}
	while (cont);

	return haystack_str;
}

U8 *StrReplace(U8 *str, U8 *old, U8 *new, I64 sff_flags=NONE, Bool free_str=FALSE)
{//Replace all instances of old with new in str. New MAlloc()ed string. free_str aids in chain replacement.
	U8 *str_start, *str_end = str, *str_loc, *tmpm = NULL;

	if (!*old)
	{
		str_start = StrNew(new);
		goto sr_end2;
	}	

	if (!StrCompare(old, new))
		goto sr_end;

	while (str_loc = str_end = StrFind(old, str_end, sff_flags))
	{
		str_start = str;
		str_end += StrLen(old); //Move start marker past old str, cutting it out
		str_start[StrLen(str_start) - StrLen(str_loc)] = '\0'; //End str_start right before where old was
		Free(tmpm);
		tmpm = MStrPrint("%s%s%s", str_start, new, str_end);
		str = tmpm;
	}
sr_end:
	str_start = StrNew(str);
sr_end2:
	if (free_str)
		Free(str);
	return str_start;
}

Bool WildMatch(U8 *test_str, U8 *wild_str)
{//Wildcard match with '*' and '?'.
	I64 ch1, ch2;
	U8 *fall_back_src = NULL, *fall_back_wild = NULL;

	while (TRUE)
	{
		if (!(ch1 = *test_str++))
		{
			if (*wild_str && *wild_str != '*')
				return FALSE;
			else
				return TRUE;
		}
		else
		{
			if (!(ch2 = *wild_str++))
				return FALSE;
			else
			{
				if (ch2 == '*')
				{
					fall_back_wild = wild_str - 1;
					fall_back_src  = test_str;
					if (!(ch2 = *wild_str++))
						return TRUE;
					while (ch2 != ch1)
						if (!(ch1 = *test_str++))
							return FALSE;
				}
				else
					if (ch2 != '?' && ch1 != ch2)
					{
						if (fall_back_wild)
						{
							wild_str = fall_back_wild;
							test_str = fall_back_src;
							fall_back_wild = NULL;
							fall_back_src  = NULL;
						}
						else
							return FALSE;
					}
			}
		}
	}
}
