I64 HasLower(U8 *src)
{
    I64 ch;

    while (ch = *src++)
        if ('a' <= ch <= 'z')
            return TRUE;
    return FALSE;
}

U0 HashFunSegFind(CHashTable *h, U8 *addr, Bool *_has_lower, U64 *_best, CHash **_res)
{
    Bool        *has_lower = *_has_lower;
    CHashExport *tmpex;
    U64          i, j, best = *_best;
    CHash       *res = *_res;

    for (i = 0; i <= h->mask; i++)
    {
        tmpex = h->body[i];
        while (tmpex)
        {
            j = 0;
            if (tmpex->type & HTT_FUN)
            {
                if (!Bt(&tmpex(CHashFun *)->flags, Cf_EXTERN) && !Bt(&tmpex(CHashFun *)->flags, Ff_INTERNAL))
                    j = tmpex(CHashFun *)->exe_addr;
            }
            else if (tmpex->type & HTT_EXPORT_SYS_SYM)
                j = tmpex->val;
            if (j)
            {
                j = addr(I64) - j;
                if (0 <= j <= best)
                {
                    if (tmpex->type & HTT_EXPORT_SYS_SYM)
                    {
                        if (j < best || j == best && !has_lower)
                        {
                            has_lower = HasLower(tmpex->str);
                            best = j;
                            res = tmpex;
                        }
                    }
                    else if (tmpex->type & HTT_FUN)
                    {
                        if (j < best || j == best && (res && res->type & HTT_EXPORT_SYS_SYM || !has_lower))
                        {
                            has_lower = HasLower(tmpex->str);
                            best = j;
                            res = tmpex;
                        }
                    }
                }
            }
            tmpex = tmpex->next;
        }
    }
    *_has_lower = has_lower;
    *_best = best;
    *_res  = res;
}

CHash *FunSegFind(U8 *addr, I64 *_offset)
{//See Hash.
    CHash       *res = NULL;
    Bool         has_lower = FALSE;
    CTask       *task;
    CHashTable  *h;
    CCPU        *c;
    U64          i, best = 0xFFFF;

    if (!CheckCodePtr(addr))
    {
        *_offset = best;
        return NULL;
    }
    if (IsDebugMode)
        for (i = 0; i < mp_count; i++)
        {
            c = &cpu_structs[i];
            task = c->executive_task;
            do
            {
                if (!TaskValidate(task))
                    goto fs_abort_task;

                h = task->hash_table;
                while (h)
                {
                    HashFunSegFind(h, addr, &has_lower, &best, &res);
                    h = h->next;
                }
                task = task->next_task;
            }
            while (task != c->executive_task);

fs_abort_task:
        }
    else
    {
        h = Fs->hash_table;
        while (h)
        {
            HashFunSegFind(h, addr, &has_lower, &best, &res);
            h = h->next;
        }
    }
    *_offset = best;

    return res;
}

U0 FunSegCacheAdd(CHash *tmps, U8 *addr)
{
    I64              i;
    CDebugInfo      *debug_info;
    CFunSegCache    *tmpfsc;

    if (tmps && tmps->type & HTT_FUN && (debug_info = tmps(CHashFun *)->debug_info))
    {
        lock i = debug.fun_seg_cache_index++;
        tmpfsc = &debug.fun_seg_cache[i & (FUN_SEG_CACHE_SIZE - 1)];
        tmpfsc->base = debug_info->body[0];
        if (addr < tmpfsc->base)
            tmpfsc->base = addr;
        tmpfsc->limit = debug_info->body[debug_info->max_line + 1 - debug_info->min_line];
        if (addr >= tmpfsc->limit)
            tmpfsc->limit = addr + 1;
        i = MinI64(StrLen(tmps->str), FUN_SEG_CACHE_STR_LEN - 1);
        MemCopy(tmpfsc->str, tmps->str, i);
        tmpfsc->str[i] = 0;
        tmpfsc->time_stamp = tS;
    }
}

U8 *FunSegCacheFind(U8 *addr, I64 *_offset)
{
    I64              i;
    F64              timeout;
    CFunSegCache    *tmpfsc = debug.fun_seg_cache;

    if (addr == SYS_IDLE_PT)
    {
        *_offset = 0;
        return "SYS_IDLE_PT";
    }
    else
    {
        timeout = tS + 8.0;
        for (i = 0; i < FUN_SEG_CACHE_SIZE; i++, tmpfsc++)
            if (tmpfsc->base <= addr < tmpfsc->limit && tmpfsc->time_stamp > timeout)
            {
                *_offset = addr - tmpfsc->base;
                return tmpfsc->str;
            }
        return NULL;
    }
}

U0 StrPrintFunSeg(U8 *buf, I64 addr, I64 field_len, I64 flags)
{
    I64          offset;
    CHashExport *tmpex;
    U8          *str, *str2;
    Bool         is_fun = FALSE;

    if (!(flags & PRINTF_TRUNCATE))
        field_len = 0;
    if (addr)
    {
        if (str = FunSegCacheFind(addr, &offset))
        {
            if (addr != SYS_IDLE_PT)
                is_fun = TRUE;
        }
        else
        {
            if (tmpex = FunSegFind(addr, &offset))
            {
                if (tmpex->type & HTT_FUN)
                    is_fun = TRUE;
                FunSegCacheAdd(tmpex, addr);
                str = tmpex->str;
            }
        }
        if (str)
        {
            if (offset > 0xFFFF)
                offset = 0xFFFF;
            if (flags & PRINTF_COMMA)
            {
                if (is_fun)
                {
                    str2 = MStrPrint("&%s", str);
                    if (!field_len)
                        StrCopy(buf, str2);
                    else if (flags & PRINTF_LEFT_JUSTIFY && StrLen(str2) < field_len)
                        StrCopy(buf, str2);
                    else
                        StrPrint(buf, "%*ts", field_len, str2);
                    Free(str2);
                }
                else
                {
                    if (!field_len)
                        StrCopy(buf, str);
                    else if (flags & PRINTF_LEFT_JUSTIFY && StrLen(str) < field_len)
                        StrCopy(buf, str);
                    else
                        StrPrint(buf, "%*ts", field_len, str);
                }
            }
            else
            {
                if (is_fun)
                {
                    str2 = MStrPrint("&%s", str);
                    if (field_len && field_len > 7)
                    {
                        if (flags & PRINTF_LEFT_JUSTIFY && StrLen(str2) < field_len - 7)
                            StrPrint(buf, "%s+0x%04X", str2, offset);
                        else
                            StrPrint(buf, "%*ts+0x%04X", field_len - 7, str2, offset);
                    }
                    else
                        StrPrint(buf, "%s+0x%04X", str2, offset);
                    Free(str2);
                }
                else
                {
                    if (field_len && field_len > 7)
                    {
                        if (flags & PRINTF_LEFT_JUSTIFY && StrLen(str) < field_len - 7)
                            StrPrint(buf, "%s+0x%04X", str, offset);
                        else
                            StrPrint(buf, "%*ts+0x%04X", field_len - 7, str, offset);
                    }
                    else
                        StrPrint(buf, "%s+0x%04X", str, offset);
                }
            }
            return;
        }
    }
    if (flags & PRINTF_COMMA)
        StrCopy(buf, ".");
    else if (flags & PRINTF_TRUNCATE && field_len)
        StrPrint(buf, "%*tX", field_len, addr);
    else
        StrPrint(buf, "%X", addr);
}

I64 SrcLineNum(U8 *addr, I64 count=1)
{//linenum for src of addr.
    CHashSrcSym *tmph;
    I64          cur_line, first_line, last_line, num_lines, offset;
    CDebugInfo  *debug_info;
    U32         *body;
    U8          *src, *src2;

    if (tmph = FunSegFind(addr, &offset))
    {
        if (tmph->type & (HTT_FUN | HTT_EXPORT_SYS_SYM))
        {
            if (debug_info = tmph->debug_info)
            {
                num_lines = debug_info->max_line - debug_info->min_line + 1;
                body = debug_info->body;

                //find first nonzero
                first_line = 0;
                while (!body[first_line])
                {
                    first_line++;
                    if (first_line >= num_lines)
                        return -1;
                }

                //find last nonzero
                last_line = num_lines - 1;
                while (!body[last_line] && last_line > first_line)
                    last_line--;

                    //interpolate to guess line num
                cur_line = ClampI64(ToF64(addr - body[first_line]) * (last_line - first_line + 1) /
                            (body[last_line] - body[first_line] + 1),
                            first_line, last_line);

                //retreat while too high
                while ((!body[cur_line] || body[cur_line] >= addr) && cur_line > first_line)
                    cur_line--;

                    //advance while to low
                while ((!body[cur_line] || body[cur_line] < addr) && cur_line < last_line)
                    cur_line++;

                if (addr < body[cur_line] + count)
                    return cur_line + debug_info->min_line;

            }
            else if (tmph->src_link)
            {
                src  = StrNew(tmph->src_link);
                src2 = StrNew(tmph->src_link);
                StrLastRemove(src, ",", src2);
                cur_line = Str2I64(src2);
                Free(src);
                Free(src2);
                return cur_line;
            }
        }
    }

    return -1;
}

U8 *SrcFileName(U8 *addr, I64 count=1, CTask *mem_task=NULL)
{//MAlloc filename for src of addr.
    CHashSrcSym *tmph;
    I64          i, j, ii, offset, best = NULL, d, best_d;
    U32         *body;
    CDebugInfo  *debug_info;
    U8          *src;

    if ((tmph = FunSegFind(addr, &offset)) && tmph->type & (HTT_FUN | HTT_EXPORT_SYS_SYM))
    {
        if (debug_info = tmph->debug_info)
        {
            j = debug_info->max_line - debug_info->min_line + 1;
            body = debug_info->body;
            best_d = I64_MAX;
            for (i = 0; i < j; i++)
            {
                if (0 < body[i] <= addr < body[i] + count)
                {
                    ii = i + 1;
                    while (!body[ii])
                        ii++;
                    if (addr < body[ii])
                    {
                        d = addr(I64) - body[i];
                        if (d < best_d)
                        {
                            best_d = d;
                            best = tmph->src_link;
                        }
                    }
                }
            }
        }
        else
            best = tmph->src_link;
    }
    if (best)
    {
        src = StrNew(best, mem_task);
        StrFirstRemove(src, ":");
        StrLastRemove(src, ",");
        return src;
    }
    else
        return NULL;
}

U8 *SrcEdLink(U8 *addr, I64 count=1, CTask *mem_task=NULL)
{//MAlloc file,line link to src of addr.
    U8 *filename, *st, *st2;
    I64 linenum;

    if (filename = SrcFileName(addr, count))
    {
        linenum = SrcLineNum(addr, count);
        if (linenum < 1)
            linenum = 1;
        st2 = MStrPrint("FL:%s,%d", filename, linenum);
        Free(filename);
        st = StrNew(st2, mem_task);
        Free(st2);
        return st;
    }

    return NULL;
}

Bool PutSrcLink(U8 *addr, I64 count=1, U8 *buf=NULL)
{//Put to StdOut a DolDoc file,line link to src of addr.
    U8 *src;

    if (src = SrcEdLink(addr, count))
    {
        if (buf)
            StrPrint(buf, "$LK,\"%p\",A=\"%s\"$", addr, src);
        else
            "$LK,\"%p\",A=\"%s\"$", addr, src;
        Free(src);
        return TRUE;
    }
    else if (buf)
        *buf = 0;

    return FALSE;
}

Bool E(U8 *addr, I64 count=512, I64 edf_dof_flags=0)
{//Edit src at addr.
    U8  *st;
    Bool res = FALSE;

    if (st = SrcEdLink(addr, count))
    {
        if (IsRaw)
            res = EdLiteFileLine(st, edf_dof_flags);
        else
            res = Ed(st, edf_dof_flags);
        Free(st);
    }

    return res;
}

Bool Man(U8 *st, I64 edf_dof_flags=0)
{//Owner's manual for symbol.  Edit src code for symbol.
    Bool          res = FALSE;
    U8          **st2;
    CHashSrcSym  *tmph;

    if (IsRaw)
    {
        if ((tmph = HashFind(st, Fs->hash_table, HTG_SRC_SYM)) && tmph->src_link)
            res = EdLiteFileLine(tmph->src_link, edf_dof_flags);
    }
    else
    {
        st2 = MStrPrint("MN:%s", st);
        res = Ed(st2, edf_dof_flags);
        Free(st2);
    }

    return res;
}