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); }