Word & Character Counter — Zig source
Live word, character, sentence, and paragraph counts plus reading-time estimate as you type.
This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.
// word-counter — Zig port: word / character / sentence / paragraph counts + 200-wpm reading-time estimate.
// Display snippet: ports the counters from the TypeScript lib (src/lib/wordCount.ts). Slices count
// bytes, so multi-byte UTF-8 diverges from the TS reference's UTF-16 units; whitespace is the ASCII
// subset of the JS /\s/ class.
const std = @import("std");
fn isSpace(c: u8) bool {
return c == ' ' or c == '\t' or c == '\n' or c == 0x0B or c == 0x0C or c == '\r';
}
/// Words: runs of non-whitespace. Empty or whitespace-only input -> 0.
pub fn countWords(t: []const u8) usize {
var n: usize = 0;
var in_word = false;
for (t) |c| {
if (isSpace(c)) { in_word = false; } else if (!in_word) { in_word = true; n += 1; }
}
return n;
}
/// Total character count, whitespace included (bytes).
pub fn countChars(t: []const u8) usize {
return t.len;
}
/// Character count with every whitespace byte removed.
pub fn countCharsNoSpaces(t: []const u8) usize {
var n: usize = 0;
for (t) |c| { if (!isSpace(c)) n += 1; }
return n;
}
/// Sentences: non-terminator runs closed by one or more of . ! ? — the TS /[^.!?]+[.!?]+/g.
pub fn countSentences(t: []const u8) usize {
var n: usize = 0;
var body = false;
for (t) |c| {
if (c == '.' or c == '!' or c == '?') {
if (body) { n += 1; body = false; }
} else body = true;
}
return n;
}
/// Paragraphs: segments split by runs of 2+ newlines; blank segments don't count.
pub fn countParagraphs(t: []const u8) usize {
var n: usize = 0;
var run: usize = 0;
var blank = true;
for (t) |c| {
if (c == '\n') {
run += 1;
if (run == 2) { if (!blank) n += 1; blank = true; } // blank line closes a paragraph
continue;
}
run = 0;
if (!isSpace(c)) blank = false;
}
return if (blank) n else n + 1;
}
/// Reading time at 200 wpm: 0 for empty, else >= 1 min; (w + 100) / 200 == Math.round.
pub fn readingTimeMin(words: i64) i64 {
if (words <= 0) return 0;
const m = @divTrunc(words + 100, 200);
return if (m < 1) 1 else m;
}
pub fn main() !void {
var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
const out = bw.writer();
const text = "CosmoDev counts words. Live totals update as you type!\n\n" ++
"Second paragraph? Two sentences - each with words. Zero logins needed.";
const w = countWords(text);
try out.print("words: {d}\n", .{w});
try out.print("chars: {d}\n", .{countChars(text)});
try out.print("no spaces: {d}\n", .{countCharsNoSpaces(text)});
try out.print("sentences: {d}\n", .{countSentences(text)});
try out.print("paragraphs: {d}\n", .{countParagraphs(text)});
try out.print("reading min: {d}\n", .{readingTimeMin(@intCast(w))});
try bw.flush();
}
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 →