first: setup

setting up repo.
This commit is contained in:
2026-08-29 13:48:15 -04:00
parent 5ffa549c89
commit c4d4f0b713
14 changed files with 1123 additions and 1 deletions
+358
View File
@@ -0,0 +1,358 @@
/*
* fastwc - a fast wc replacement.
*
* Kickstart stub: functionally correct, with the standard fast-counting
* tricks already in place (memchr for newlines, a whitespace lookup table
* plus popcount for words). The next level of speed (SIMD / SWAR bulk
* scanning) plugs into count_stream() below.
*/
#define _POSIX_C_SOURCE 200809L
#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <wctype.h>
enum {
F_LINES = 1 << 0, /* -l: count '\n' */
F_WORDS = 1 << 1, /* -w: whitespace-separated tokens */
F_CHARS = 1 << 2, /* -m: multibyte characters */
F_BYTES = 1 << 3, /* -c: bytes */
};
static int flags = 0;
typedef struct {
long long lines;
long long words;
long long chars;
long long bytes;
int ok; /* read succeeded */
} counts_t;
static unsigned char ws_tab[256]; /* ws_tab[c] = 1 if c is whitespace */
static void init_ws_tab(void)
{
for (int i = 0; i < 256; i++)
ws_tab[i] = isspace((unsigned char)i) ? 1 : 0;
}
static void usage(FILE *out)
{
fprintf(out,
"usage: fastwc [-lwc] [-m] [file...]\n"
"\n"
"Count lines, words, and bytes (default) or selected counts.\n"
"With no file, or when file is -, read standard input.\n"
"\n"
" -l count lines\n"
" -w count words\n"
" -c count bytes\n"
" -m count characters\n"
" --help display this help and exit\n"
" --version output version information and exit\n");
}
/*
* Count '\n' in fixed 8-byte SWAR chunks. XOR turns '\n' bytes into zero
* bytes, the classic has-zero-byte trick flags them, popcount sums them.
* Constant stride (no per-newline memchr calls), so it stays fast even on
* files with dense newlines.
*/
static long long count_newlines(const unsigned char *s, size_t n)
{
const uint64_t nl = 0x0a0a0a0a0a0a0a0aULL;
const uint64_t lo = 0x0101010101010101ULL;
const uint64_t hi = 0x8080808080808080ULL;
const uint64_t cl = 0x7f7f7f7f7f7f7f7fULL;
long long k = 0;
size_t i = 0;
for (; i + 8 <= n; i += 8) {
uint64_t x;
memcpy(&x, s + i, 8);
x = (x ^ nl) & cl;
k += (long long)__builtin_popcountll((x - lo) & ~x & hi);
}
for (; i < n; i++)
k += s[i] == '\n';
return k;
}
/*
* Count word starts (whitespace -> non-whitespace transitions) 8 bytes at
* a time. For a chunk, build a bitmask where bit j = 1 if byte j is
* whitespace; word starts inside the chunk are the 1->0 transitions of
* that mask, plus one for the left edge if the previous byte was
* whitespace. *prev_ws carries the boundary across chunks.
*/
static long long count_words(const unsigned char *s, size_t n, int *prev_ws)
{
long long w = 0;
size_t i = 0;
int prev = *prev_ws;
for (; i + 8 <= n; i += 8) {
uint8_t m = 0;
m |= (uint8_t)ws_tab[s[i + 0]] << 0;
m |= (uint8_t)ws_tab[s[i + 1]] << 1;
m |= (uint8_t)ws_tab[s[i + 2]] << 2;
m |= (uint8_t)ws_tab[s[i + 3]] << 3;
m |= (uint8_t)ws_tab[s[i + 4]] << 4;
m |= (uint8_t)ws_tab[s[i + 5]] << 5;
m |= (uint8_t)ws_tab[s[i + 6]] << 6;
m |= (uint8_t)ws_tab[s[i + 7]] << 7;
/* bit j set iff byte j-1 was whitespace and byte j is not */
w += (long long)__builtin_popcount((unsigned)((uint8_t)~m & (m << 1)));
if (prev && !(m & 1))
w++;
prev = (m >> 7) & 1;
}
for (; i < n; i++) {
int ws = ws_tab[s[i]];
if (prev && !ws)
w++;
prev = ws;
}
*prev_ws = prev;
return w;
}
/*
* Multibyte (-m) path: decode each character with mbrtowc, carrying
* incomplete sequences across read boundaries. Only used when -m is
* requested, so it stays deliberately simple.
*/
static void count_stream_mb(FILE *fp, counts_t *c)
{
static unsigned char buf[1 << 17];
mbstate_t st;
size_t pend = 0, nread;
int prev_ws = 1;
memset(&st, 0, sizeof st);
while ((nread = fread(buf + pend, 1, sizeof buf - pend, fp)) > 0) {
size_t n = nread + pend;
size_t i = 0;
c->bytes += (long long)nread;
while (i < n) {
wchar_t wc;
size_t r;
if (buf[i] < 0x80) {
wc = buf[i];
r = 1;
} else {
r = mbrtowc(&wc, (const char *)buf + i, n - i, &st);
if (r == (size_t)-2) { /* incomplete: carry over */
pend = n - i;
memmove(buf, buf + i, pend);
break;
}
if (r == (size_t)-1) { /* invalid sequence */
memset(&st, 0, sizeof st);
wc = L'\xfffd';
r = 1;
}
}
if (wc == L'\n')
c->lines++;
if (iswspace(wc)) {
prev_ws = 1;
} else if (prev_ws) {
c->words++;
prev_ws = 0;
}
c->chars++;
i += r;
}
if (i >= n)
pend = 0;
}
if (ferror(fp))
c->ok = 0;
}
/* Fast path (-l/-w/-c): one pass, per-chunk memchr + table counting. */
static void count_stream(FILE *fp, counts_t *c)
{
static unsigned char buf[1 << 17]; /* 128 KiB */
size_t nread;
int prev_ws = 1; /* start of file: as if preceded by whitespace */
if (flags & F_CHARS) {
count_stream_mb(fp, c);
return;
}
while ((nread = fread(buf, 1, sizeof buf, fp)) > 0) {
c->bytes += (long long)nread;
if (flags & F_LINES)
c->lines += count_newlines(buf, nread);
if (flags & F_WORDS)
c->words += count_words(buf, nread, &prev_ws);
}
if (ferror(fp))
c->ok = 0;
}
static void count_file(const char *path, counts_t *c)
{
FILE *fp;
if (strcmp(path, "-") == 0) {
fp = stdin;
} else {
fp = fopen(path, "rb");
if (fp == NULL) {
fprintf(stderr, "fastwc: %s: %s\n", path, strerror(errno));
c->ok = 0;
return;
}
}
count_stream(fp, c);
if (ferror(fp))
fprintf(stderr, "fastwc: %s: read error: %s\n",
strcmp(path, "-") == 0 ? "standard input" : path,
strerror(errno));
if (fp != stdin)
fclose(fp);
}
static int col_width(long long v)
{
int w = 1;
while (v >= 10) {
v /= 10;
w++;
}
return w;
}
int main(int argc, char **argv)
{
counts_t *rows;
int nfiles = 0;
int failed = 0;
int i, a;
init_ws_tab();
for (a = 1; a < argc; a++) {
const char *arg = argv[a];
if (arg[0] != '-' || arg[1] == '\0')
break; /* first file argument */
if (strcmp(arg, "--") == 0) {
a++;
break;
}
if (strcmp(arg, "--help") == 0) {
usage(stdout);
return 0;
}
if (strcmp(arg, "--version") == 0) {
printf("fastwc 0.1.0\n");
return 0;
}
for (const char *p = arg + 1; *p; p++) {
switch (*p) {
case 'l': flags |= F_LINES; break;
case 'w': flags |= F_WORDS; break;
case 'c': flags |= F_BYTES; break;
case 'm': flags |= F_CHARS; break;
default:
fprintf(stderr, "fastwc: invalid option -- '%c'\n", *p);
usage(stderr);
return 1;
}
}
}
if (flags == 0)
flags = F_LINES | F_WORDS | F_BYTES; /* wc default: -l -w -c */
if (flags & F_CHARS)
setlocale(LC_CTYPE, "");
nfiles = argc - a;
if (nfiles == 0) {
rows = calloc(1, sizeof *rows);
rows[0].ok = 1;
count_stream(stdin, &rows[0]);
if (!rows[0].ok) {
fprintf(stderr, "fastwc: standard input: read error: %s\n",
strerror(errno));
failed = 1;
}
nfiles = 1;
} else {
rows = calloc((size_t)nfiles, sizeof *rows);
for (i = 0; i < nfiles; i++) {
rows[i].ok = 1;
count_file(argv[a + i], &rows[i]);
if (!rows[i].ok)
failed = 1;
}
}
/* Column widths: widest count in each column across rows + total. */
int wl = 1, ww = 1, wm = 1, wb = 1;
long long tl = 0, tw = 0, tm = 0, tb = 0;
for (i = 0; i < nfiles; i++) {
counts_t *r = &rows[i];
int x;
tl += r->lines; tw += r->words; tm += r->chars; tb += r->bytes;
if ((flags & F_LINES) && (x = col_width(r->lines)) > wl) wl = x;
if ((flags & F_WORDS) && (x = col_width(r->words)) > ww) ww = x;
if ((flags & F_CHARS) && (x = col_width(r->chars)) > wm) wm = x;
if ((flags & F_BYTES) && (x = col_width(r->bytes)) > wb) wb = x;
}
if ((flags & F_LINES) && col_width(tl) > wl) wl = col_width(tl);
if ((flags & F_WORDS) && col_width(tw) > ww) ww = col_width(tw);
if ((flags & F_CHARS) && col_width(tm) > wm) wm = col_width(tm);
if ((flags & F_BYTES) && col_width(tb) > wb) wb = col_width(tb);
for (i = 0; i < nfiles; i++) {
counts_t *r = &rows[i];
if (flags & F_LINES) printf("%*lld ", wl, r->lines);
if (flags & F_WORDS) printf("%*lld ", ww, r->words);
if (flags & F_CHARS) printf("%*lld ", wm, r->chars);
if (flags & F_BYTES) printf("%*lld ", wb, r->bytes);
if (argc - a > 0)
printf("%s", argv[a + i]);
printf("\n");
}
if (argc - a > 1) {
if (flags & F_LINES) printf("%*lld ", wl, tl);
if (flags & F_WORDS) printf("%*lld ", ww, tw);
if (flags & F_CHARS) printf("%*lld ", wm, tm);
if (flags & F_BYTES) printf("%*lld ", wb, tb);
printf("total\n");
}
free(rows);
return failed ? 1 : 0;
}