git-trace

romele / git-trace public

62 commits · 1 branch · 1 tag · 1 contributor (top: Romele) · first commit 3d ago · last push 11m ago

src/highlight.c

#include "highlight.h"

#include "util.h"

#include <ctype.h>
#include <string.h>

/* ---- language profiles ------------------------------------------------ */

static const char *const kw_c[] = {
    "auto","break","case","char","const","continue","default","do",
    "double","else","enum","extern","float","for","goto","if","inline",
    "int","long","register","restrict","return","short","signed",
    "sizeof","static","struct","switch","typedef","union","unsigned",
    "void","volatile","while","_Alignas","_Alignof","_Atomic","_Bool",
    "_Complex","_Generic","_Imaginary","_Noreturn","_Static_assert",
    "_Thread_local","NULL","size_t", NULL
};

/* one list for shell + python + config-ish files: overlap is rare and a
 * couple of generous keywords beat two nearly-identical tables */
static const char *const kw_sh[] = {
    "and","as","assert","break","case","class","continue","def","del",
    "do","done","elif","else","esac","except","fi","finally","for",
    "from","function","global","if","import","in","is","lambda","local",
    "nonlocal","not","or","pass","raise","readonly","return","select",
    "then","try","until","while","with","yield",
    "True","False","None","echo","printf","export","shift","eval",
    "exec","trap","set","unset","cd","source", NULL
};

struct lang {
    const char *const *exts;      /* ".c", ".h" ... (dot included)   */
    const char *const *names;     /* exact basenames ("Makefile")    */
    const char *const *kw;
    int lcomm;                    /* line-comment leader: '/' or '#' */
    int bcomm;                    /* slash-star block comments        */
    int preproc;                  /* '#' at line start = directive   */
};

static const char *const ext_c[] =
    { ".c",".h",".cc",".hh",".cpp",".hpp",".cxx",".hxx", NULL };
static const char *const ext_sh[] =
    { ".sh",".bash",".zsh",".ksh",".py",".pl",".pm",".rb",
      ".yml",".yaml",".mk",".conf",".cfg",".toml", NULL };
static const char *const nm_sh[] =
    { "Makefile","makefile","Dockerfile","GNUmakefile", NULL };

static const struct lang langs[] = {
    { ext_c,  NULL,  kw_c,  '/', 1, 1 },
    { ext_sh, nm_sh, kw_sh, '#', 0, 0 },
};

/* ---- tokenizer -------------------------------------------------------- */

static int is_ident0(int c) { return isalpha(c) || c == '_'; }
static int is_ident (int c) { return isalnum(c) || c == '_'; }

static const struct lang *pick_lang(const char *path)
{
    const char *base = strrchr(path, '/');
    base = base ? base + 1 : path;
    const char *dot = strrchr(base, '.');
    for (size_t i = 0; i < sizeof langs / sizeof langs[0]; i++) {
        const struct lang *L = &langs[i];
        if (dot)
            for (int j = 0; L->exts[j]; j++)
                if (strcmp(dot, L->exts[j]) == 0)
                    return L;
        if (L->names)
            for (int j = 0; L->names[j]; j++)
                if (strcmp(base, L->names[j]) == 0)
                    return L;
    }
    return NULL;
}

static int is_kw(const struct lang *L, const char *w, size_t n)
{
    for (int i = 0; L->kw[i]; i++)
        if (strlen(L->kw[i]) == n && memcmp(L->kw[i], w, n) == 0)
            return 1;
    return 0;
}

/* html-escape a run into b, wrapped in <span> when cls != NULL */
static void emit(struct buf *b, const char *p, size_t n, const char *cls)
{
    if (!n)
        return;
    if (cls) {
        buf_puts(b, "<span class=");
        buf_puts(b, cls);
        buf_puts(b, ">");
    }
    buf_put_escaped(b, p, n);
    if (cls)
        buf_puts(b, "</span>");
}

/* consume a string/char literal starting at i; returns index past it */
static size_t scan_str(const char *s, size_t n, size_t i)
{
    int q = s[i];
    for (i++; i < n; i++) {
        if (s[i] == '\\' && i + 1 < n) {
            i++;
            continue;
        }
        if (s[i] == q)
            return i + 1;
        if (s[i] == '\n')
            return i;           /* unterminated on this line */
    }
    return n;
}

/* consume from '#' at line start through the directive's end,
 * honoring backslash-newline continuations */
static size_t scan_preproc(const char *s, size_t n, size_t i)
{
    for (; i < n; i++) {
        if (s[i] == '\n') {
            if (i > 0 && s[i - 1] == '\\')
                continue;
            return i;
        }
    }
    return n;
}

void hl_emit(struct buf *b, const char *data, size_t len,
             const char *path)
{
    const struct lang *L = pick_lang(path);
    if (!L) {
        buf_put_escaped(b, data, len);
        return;
    }

    size_t plain = 0, i = 0;
    int bol = 1;                         /* beginning-of-line */
    while (i < len) {
        size_t tok = 0;
        const char *cls = NULL;
        int c = (unsigned char)data[i];

        if (L->lcomm == '/' && c == '/' && i + 1 < len &&
            data[i + 1] == '/') {
            tok = i;  while (tok < len && data[tok] != '\n') tok++;
            cls = "hl-c";
        } else if (L->bcomm && c == '/' && i + 1 < len &&
                   data[i + 1] == '*') {
            tok = i + 2;
            while (tok + 1 < len &&
                   !(data[tok] == '*' && data[tok + 1] == '/'))
                tok++;
            tok = tok + 1 < len ? tok + 2 : len;
            cls = "hl-c";
        } else if (c == '#' && (L->lcomm == '#' ||
                   (L->preproc && bol))) {
            tok = L->preproc ? scan_preproc(data, len, i) : i;
            if (!L->preproc)
                while (tok < len && data[tok] != '\n') tok++;
            cls = L->preproc && bol ? "hl-p" : "hl-c";
        } else if (c == '"' || c == '\'') {
            tok = scan_str(data, len, i);
            cls = "hl-s";
        } else if (is_ident0(c)) {
            tok = i;  while (tok < len && is_ident((unsigned char)data[tok])) tok++;
            cls = is_kw(L, data + i, tok - i) ? "hl-k" : NULL;
        } else if (isdigit(c)) {
            tok = i;
            while (tok < len &&
                   (isalnum((unsigned char)data[tok]) ||
                    data[tok] == '.' || data[tok] == '\'' ||
                    data[tok] == '_'))
                tok++;
            cls = "hl-n";
        }

        if (cls) {
            emit(b, data + plain, i - plain, NULL);   /* flush plain */
            emit(b, data + i, tok - i, cls);
            i = tok;
            plain = i;
            bol = 0;
        } else {
            if (c == '\n')
                bol = 1;
            else if (!isspace(c))
                bol = 0;
            i++;
        }
    }
    emit(b, data + plain, len - plain, NULL);
}