aboutsummaryrefslogtreecommitdiff
path: root/src/work.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/work.c')
-rw-r--r--src/work.c453
1 files changed, 453 insertions, 0 deletions
diff --git a/src/work.c b/src/work.c
new file mode 100644
index 0000000..37fd8b8
--- /dev/null
+++ b/src/work.c
@@ -0,0 +1,453 @@
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <stdatomic.h>
+#include <stdckdint.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <alloc.h>
+#include <array.h>
+#include <errors.h>
+#include <macros.h>
+#include <mbstring.h>
+#include <pcre2.h>
+#include <unicode/string.h>
+
+#include "exitcodes.h"
+#include "flags.h"
+#include "work.h"
+
+#define DEFINE_OPERATOR(fn) \
+ void operator_##fn(ptrdiff_t opi, u8view_t sv, u8view_t **hl)
+#define array_extend_sv(xs, sv) \
+ array_extend((xs), (sv).p, (ptrdiff_t)(sv).len)
+
+typedef struct {
+ ptrdiff_t row, col;
+} pos_t;
+
+static pos_t compute_pos(const char8_t *p);
+static bool islbrk(u8view_t g);
+static int svposcmp(const void *a, const void *b);
+static void write_match_to_buffer(u8view_t sv, u8view_t *hl);
+
+static DEFINE_OPERATOR(dispatch);
+static DEFINE_OPERATOR(g);
+static DEFINE_OPERATOR(G);
+static DEFINE_OPERATOR(h);
+static DEFINE_OPERATOR(H);
+static DEFINE_OPERATOR(x);
+static DEFINE_OPERATOR(X);
+
+static thread_local const char *filename;
+static thread_local char8_t *baseptr;
+static thread_local const char8_t *last_match;
+static thread_local unsigned char **buf;
+
+static typeof(operator_dispatch) *operators[] = {
+ ['g'] = operator_g,
+ ['G'] = operator_G,
+ ['h'] = operator_h,
+ ['H'] = operator_H,
+ ['x'] = operator_x,
+ ['X'] = operator_X,
+};
+
+extern atomic_int rv;
+extern op_t *ops;
+extern bool cflag;
+extern typeof(pcre2_match) *pcre2_match_fn;
+
+
+
+void
+process_file(const char *locl_filename, unsigned char **locl_buf)
+{
+ filename = locl_filename;
+ buf = locl_buf;
+
+ FILE *fp = streq(filename, "-") ? stdin : fopen(filename, "r");
+ if (fp == nullptr) {
+ warn("fopen: %s:", filename);
+ atomic_store(&rv, EXIT_WARNING);
+ goto out;
+ }
+
+ allocator_t mem = init_heap_allocator(nullptr);
+ if (baseptr == nullptr)
+ baseptr = array_new(mem, char8_t, 0x1000);
+ size_t bufsz = array_cap(baseptr);
+ last_match = baseptr;
+
+ do {
+ static_assert(sizeof(char8_t) == 1, "sizeof(char8_t) != 1; wtf?");
+ baseptr = array_resz(baseptr, bufsz += BUFSIZ); /* TODO: Bounds checking */
+ size_t n = fread(baseptr + array_len(baseptr), 1, BUFSIZ, fp);
+ array_hdr(baseptr)->len += n;
+ } while (!feof(fp));
+
+ if (ferror(fp)) {
+ warn("fread: %s:", filename);
+ atomic_store(&rv, EXIT_WARNING);
+ goto out;
+ }
+
+ /* Shouldn’t need more than 32 ever… */
+ static thread_local u8view_t *hl = nullptr;
+ if (hl == nullptr)
+ hl = array_new(mem, typeof(*hl), 32);
+
+ operator_dispatch(0, (u8view_t){baseptr, array_len(baseptr)}, &hl);
+#if DEBUG
+ array_free(baseptr);
+ baseptr = nullptr;
+ array_free(hl);
+ hl = nullptr;
+#else
+ array_hdr(baseptr)->len = 0;
+ array_hdr(hl)->len = 0;
+#endif
+
+out:
+ if (fp != stdin)
+ (void)fclose(fp);
+}
+
+
+
+DEFINE_OPERATOR(dispatch)
+{
+ if (array_len(ops) == opi) {
+ if (flags.p)
+ exit(EXIT_SUCCESS);
+ atomic_compare_exchange_strong(&rv, &(int){EXIT_NOMATCH}, EXIT_SUCCESS);
+ write_match_to_buffer(sv, *hl);
+ } else /* Cast to silence GCC warning */
+ operators[(unsigned char)ops[opi].c](opi, sv, hl);
+}
+
+DEFINE_OPERATOR(g)
+{
+ pcre2_match_data *md =
+ pcre2_match_data_create_from_pattern(ops[opi].re, nullptr);
+ int n = pcre2_match_fn(ops[opi].re, sv.p, sv.len, 0, PCRE2_NOTEMPTY,
+ md, nullptr);
+ pcre2_match_data_free(md);
+
+ /* This should never happen */
+ if (n == 0)
+ cerr(EXIT_FATAL, "PCRE2 match data too small");
+ if (n == PCRE2_ERROR_NOMATCH)
+ return;
+ if (n < 0)
+ ; /* TODO: Handle error */
+
+ operator_dispatch(opi + 1, sv, hl);
+}
+
+DEFINE_OPERATOR(G)
+{
+ /* TODO: Can we reuse match data? */
+ pcre2_match_data *md =
+ pcre2_match_data_create_from_pattern(ops[opi].re, nullptr);
+ int n = pcre2_match_fn(ops[opi].re, sv.p, sv.len, 0, PCRE2_NOTEMPTY,
+ md, nullptr);
+ pcre2_match_data_free(md);
+
+ /* This should never happen */
+ if (n == 0)
+ cerr(EXIT_FATAL, "PCRE2 match data too small");
+ if (n == PCRE2_ERROR_NOMATCH)
+ operator_dispatch(opi + 1, sv, hl);
+ if (n < 0)
+ ; /* TODO: Handle error */
+}
+
+DEFINE_OPERATOR(h)
+{
+ if (flags.p) {
+ operator_dispatch(opi + 1, sv, hl);
+ return;
+ }
+
+ pcre2_match_data *md =
+ pcre2_match_data_create_from_pattern(ops[opi].re, nullptr);
+ u8view_t sv_save = sv;
+ ptrdiff_t origlen = array_len(*hl);
+ for (;;) {
+ int n = pcre2_match_fn(ops[opi].re, sv.p, sv.len, 0,
+ PCRE2_NOTEMPTY, md, nullptr);
+ /* This should never happen */
+ if (n == 0)
+ cerr(EXIT_FATAL, "PCRE2 match data too small");
+ if (n == PCRE2_ERROR_NOMATCH)
+ break;
+ if (n < 0)
+ ; /* TODO: Handle error */
+
+ size_t *ov = pcre2_get_ovector_pointer(md);
+ array_push(hl, ((u8view_t){sv.p + ov[0], ov[1] - ov[0]}));
+ VSHFT(&sv, ov[1]);
+ }
+ pcre2_match_data_free(md);
+ operator_dispatch(opi + 1, sv_save, hl);
+ array_hdr(*hl)->len = origlen;
+}
+
+DEFINE_OPERATOR(H)
+{
+ if (flags.p) {
+ operator_dispatch(opi + 1, sv, hl);
+ return;
+ }
+
+ pcre2_match_data *md =
+ pcre2_match_data_create_from_pattern(ops[opi].re, nullptr);
+ u8view_t sv_save = sv;
+ ptrdiff_t origlen = array_len(*hl);
+ for (;;) {
+ int n = pcre2_match_fn(ops[opi].re, sv.p, sv.len, 0, PCRE2_NOTEMPTY,
+ md, nullptr);
+ /* This should never happen */
+ if (n == 0)
+ cerr(EXIT_FATAL, "PCRE2 match data too small");
+ if (n == PCRE2_ERROR_NOMATCH)
+ break;
+ if (n < 0)
+ ; /* TODO: Handle error */
+
+ size_t *ov = pcre2_get_ovector_pointer(md);
+ array_push(hl, ((u8view_t){sv.p, ov[0]}));
+ VSHFT(&sv, ov[1]);
+ }
+ pcre2_match_data_free(md);
+ operator_dispatch(opi + 1, sv_save, hl);
+ array_hdr(*hl)->len = origlen;
+}
+
+DEFINE_OPERATOR(x)
+{
+ pcre2_match_data *md =
+ pcre2_match_data_create_from_pattern(ops[opi].re, nullptr);
+ for (;;) {
+ int n = pcre2_match_fn(ops[opi].re, sv.p, sv.len, 0, PCRE2_NOTEMPTY,
+ md, nullptr);
+ /* This should never happen */
+ if (n == 0)
+ cerr(EXIT_FATAL, "PCRE2 match data too small");
+ if (n == PCRE2_ERROR_NOMATCH)
+ break;
+ if (n < 0)
+ ; /* TODO: Handle error */
+
+ size_t *ov = pcre2_get_ovector_pointer(md);
+ operator_dispatch(opi + 1, (u8view_t){sv.p + ov[0], ov[1] - ov[0]}, hl);
+ VSHFT(&sv, ov[1]);
+ }
+ pcre2_match_data_free(md);
+}
+
+DEFINE_OPERATOR(X)
+{
+ pcre2_match_data *md =
+ pcre2_match_data_create_from_pattern(ops[opi].re, nullptr);
+ for (;;) {
+ int n = pcre2_match_fn(ops[opi].re, sv.p, sv.len, 0, PCRE2_NOTEMPTY,
+ md, nullptr);
+ /* This should never happen */
+ if (n == 0)
+ cerr(EXIT_FATAL, "PCRE2 match data too small");
+ if (n == PCRE2_ERROR_NOMATCH)
+ break;
+ if (n < 0)
+ ; /* TODO: Handle error */
+
+ size_t *ov = pcre2_get_ovector_pointer(md);
+ if (ov[0] != 0)
+ operator_dispatch(opi + 1, (u8view_t){sv.p, ov[0]}, hl);
+ VSHFT(&sv, ov[1]);
+ }
+ if (sv.len != 0)
+ operator_dispatch(opi + 1, sv, hl);
+ pcre2_match_data_free(md);
+}
+
+
+
+static inline bool
+views_overlap(const u8view_t a, const u8view_t b)
+{
+ const char8_t *p = a.p + a.len;
+ return p >= b.p && p <= b.p + b.len;
+}
+
+void
+write_match_to_buffer(u8view_t sv, u8view_t *hl)
+{
+ const u8view_t COL_FN = !flags.c ? U8("") : U8("\33[35m");
+ const u8view_t COL_HL = !flags.c ? U8("") : U8("\33[01;31m");
+ const u8view_t COL_LN = !flags.c ? U8("") : U8("\33[32m");
+ const u8view_t COL_SE = !flags.c ? U8("") : U8("\33[36m");
+ const u8view_t COL_RS = !flags.c ? U8("") : U8("\33[0m");
+
+ if (
+#if GIT_GRAB
+ true
+#else
+ flags.do_header
+#endif
+ ) {
+ char sep = flags.z ? 0 : ':';
+
+ size_t filenamesz = strlen(filename);
+
+ array_extend_sv(buf, COL_FN);
+ array_extend(buf, filename, (ptrdiff_t)filenamesz);
+ array_extend_sv(buf, COL_RS);
+
+ array_extend_sv(buf, COL_SE);
+ array_push(buf, sep);
+ array_extend_sv(buf, COL_RS);
+
+ /* GCC things ‘offset’ can overflow because our offsets have type
+ ptrdiff_t which if negative would have a ‘-’ in the front, but
+ we know that the match positions can’t be negative so it’s
+ safe to ignore. */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-overflow"
+
+ int offsetsz;
+ char offset[/* len(INT64_MAX - 1) */ 19];
+ if (flags.l) {
+ pos_t p = compute_pos(sv.p);
+
+ offsetsz = sprintf(offset, "%td", p.row + 1);
+ array_extend_sv(buf, COL_LN);
+ array_extend(buf, offset, offsetsz);
+ array_extend_sv(buf, COL_RS);
+
+ array_extend_sv(buf, COL_SE);
+ array_push(buf, sep);
+ array_extend_sv(buf, COL_RS);
+
+ offsetsz = sprintf(offset, "%td", p.col + 1);
+ array_extend_sv(buf, COL_LN);
+ array_extend(buf, offset, offsetsz);
+ array_extend_sv(buf, COL_RS);
+ } else {
+ offsetsz = sprintf(offset, "%td", sv.p - baseptr);
+ array_extend_sv(buf, COL_LN);
+ array_extend(buf, offset, offsetsz);
+ array_extend_sv(buf, COL_RS);
+ }
+
+ array_extend_sv(buf, COL_SE);
+ array_push(buf, sep);
+ array_extend_sv(buf, COL_RS);
+ }
+
+#pragma GCC diagnostic pop
+
+ /* Here we need to take all the views of regions to highlight, and try
+ to merge them into a simpler form. This happens in two steps:
+
+ 1. Sort the views by their starting position in the matched text.
+ 2. Merge overlapping views.
+
+ After this process we should have the most reduced possible set of
+ views. The next part is to actually print the highlighted regions
+ possible which requires bounds-checking as highlighted regions may
+ begin before or end after the matched text when using patterns such
+ as ‘h/.+/ x/.$/’. */
+
+ static thread_local u8view_t *sorted = nullptr;
+ if (sorted == nullptr) {
+ allocator_t mem = init_heap_allocator(nullptr);
+ ptrdiff_t buflen = array_len(hl);
+ buflen = MAX(buflen, 16);
+ sorted = array_new(mem, typeof(*sorted), buflen);
+ } else
+ array_hdr(sorted)->len = 0;
+
+ array_extend(&sorted, hl, array_len(hl));
+ qsort(sorted, array_len(sorted), sizeof(*sorted), svposcmp);
+
+ for (ptrdiff_t i = 0, len = array_len(sorted); i < len - 1;) {
+ if (views_overlap(sorted[i], sorted[i + 1])) {
+ sorted[i].len = sorted[i + 1].p + sorted[i + 1].len - sorted[i].p;
+ memmove(hl + i + 1, hl + i + 2, sizeof(*hl) * (len - i - 1));
+ array_hdr(sorted)->len = --len;
+ } else
+ i++;
+ }
+
+ for (ptrdiff_t i = 0, len = array_len(sorted); i < len; i++) {
+ if (i < len - 1 && sorted[i].p == sorted[i + 1].p)
+ continue;
+ array_extend(buf, sv.p, sorted[i].p - sv.p);
+ array_extend_sv(buf, COL_HL);
+ array_extend_sv(buf, sorted[i]);
+ array_extend_sv(buf, COL_RS);
+ ptrdiff_t Δ = sorted[i].p - sv.p + sorted[i].len;
+ VSHFT(&sv, Δ);
+ }
+ array_extend_sv(buf, sv);
+
+#if DEBUG
+ array_free(sorted);
+ sorted = nullptr;
+#endif
+
+ if (flags.z)
+ array_push(buf, 0);
+ else {
+ ptrdiff_t bufsz = array_len(*buf);
+ if (!flags.s || bufsz == 0 || (*buf)[bufsz - 1] != '\n')
+ array_push(buf, '\n');
+ }
+}
+
+pos_t
+compute_pos(const char8_t *ptr)
+{
+ static thread_local pos_t p;
+ if (last_match == baseptr)
+ p.row = p.col = 0;
+ u8view_t g, sv = {last_match, PTRDIFF_MAX};
+ while (sv.p < ptr) {
+ ucsgnext(&g, &sv);
+ if (islbrk(g)) {
+ p.row++;
+ p.col = 0;
+ } else
+ p.col = ucswdth(g, p.col, 8); /* TODO: Configurable tabsize? */
+ }
+ last_match = sv.p;
+ return p;
+}
+
+bool
+islbrk(u8view_t g)
+{
+ return ucseq(g, U8("\n"))
+ || ucseq(g, U8("\v"))
+ || ucseq(g, U8("\f"))
+ || ucseq(g, U8("\r\n"))
+ || ucseq(g, U8("\x85"))
+ || ucseq(g, U8("\u2028"))
+ || ucseq(g, U8("\u2029"));
+}
+
+int
+svposcmp(const void *a_, const void *b_)
+{
+ const u8view_t *a = a_,
+ *b = b_;
+ ptrdiff_t Δ = a->p - b->p;
+ return Δ == 0 ? (ptrdiff_t)a->len - (ptrdiff_t)b->len : Δ;
+}