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 Lex() can use them with Token Codes.
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 flags 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;
                    }
            }
        }
    }
}