Skip to content

Word & Character Counter — C source

Live word, character, sentence, and paragraph counts plus reading-time estimate as you type.

This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.

/* word-counter — C port: word / character / sentence / paragraph counts + 200-wpm reading-time estimate.
   Display snippet: ports the counters from the TypeScript lib (src/lib/wordCount.ts). C strings are
   bytes, so counts are byte counts — multi-byte UTF-8 diverges from the TS reference's UTF-16 units. */
#include <ctype.h>
#include <stdio.h>
#include <string.h>

/* Words: runs of non-whitespace (ASCII subset of the JS /\s/). Blank input -> 0. */
static long count_words(const char *t) {
    long n = 0;
    int in_word = 0;
    for (; *t; t++) {
        if (isspace((unsigned char)*t)) in_word = 0;
        else if (!in_word) { in_word = 1; n++; }
    }
    return n;
}

/* Total character count, whitespace included. */
static long count_chars(const char *t) { return (long)strlen(t); }
/* Character count with every whitespace byte removed. */
static long count_chars_no_spaces(const char *t) {
    long n = 0;
    for (; *t; t++)
        if (!isspace((unsigned char)*t)) n++;
    return n;
}

/* Sentences: maximal runs of non-terminators each closed by one or more of
   . ! ? — the TS regex /[^.!?]+[.!?]+/g as a single scan. */
static long count_sentences(const char *t) {
    long n = 0;
    int body = 0;
    for (; *t; t++) {
        if (*t == '.' || *t == '!' || *t == '?') {
            if (body) { n++; body = 0; }
        } else {
            body = 1;
        }
    }
    return n;
}

/* Paragraphs: segments split by runs of 2+ newlines; blank segments don't
   count (the TS split(/\n{2,}/).filter(s => s.trim())). */
static long count_paragraphs(const char *t) {
    long n = 0, run = 0;
    int blank = 1;
    for (; *t; t++) {
        if (*t == '\n') {
            if (++run == 2) { /* blank line closes the paragraph */
                if (!blank) n++;
                blank = 1;
            }
            continue;
        }
        run = 0;
        if (!isspace((unsigned char)*t)) blank = 0;
    }
    return blank ? n : n + 1;
}
/* Reading time at 200 wpm: 0 for empty, else >= 1 min; (w + 100) / 200 == Math.round(w / 200). */
static long reading_time_min(long words) {
    if (words <= 0) return 0;
    long m = (words + 100) / 200;
    return m < 1 ? 1 : m;
}

int main(void) {
    const char *text = "CosmoDev counts words. Live totals update as you type!\n\n"
                       "Second paragraph? Two sentences - each with words. Zero logins needed.";
    long w = count_words(text);
    printf("words:       %ld\n", w);                           /* 20 */
    printf("chars:       %ld\n", count_chars(text));           /* 126 */
    printf("no spaces:   %ld\n", count_chars_no_spaces(text)); /* 106 */
    printf("sentences:   %ld\n", count_sentences(text));       /* 5 */
    printf("paragraphs:  %ld\n", count_paragraphs(text));      /* 2 */
    printf("reading min: %ld\n", reading_time_min(w));         /* 1 */
    return 0;
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →