ASCII Table — C source
A searchable, filterable reference for all 128 ASCII code points. See the decimal, hex, octal, and binary form of every character, narrow to printable characters only, clamp the code-point range, and click any row to copy. Runs 100% client-side.
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/*
* ASCII Table — pure logic (C port): the 128-entry ASCII reference table with
* search and range filters.
*
* Language: C11 — standard library only (stdio / string / ctype / stdbool).
* Source: CosmoDev polyglot showcase port; canonical = src/lib/ascii-table.ts
* + this tool's python.py / rust.rs.
* License: display source — part of CosmoDev's polyglot tool pages.
*
* Each of the 128 ASCII code points is described by its decimal / hex / octal /
* binary forms, a display glyph, a human name, whether it is printable, whether
* it is a control character, and (for control chars that have one) a C-style
* escape sequence.
*/
#include <ctype.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#define ASCII_TABLE_SIZE 128
/*
* A single ASCII code point and its derived representations.
* All strings are fixed-size buffers (C has no dynamic strings): every field
* is sized for its longest value plus the NUL terminator.
*/
typedef struct {
int dec; /* Decimal code point, 0–127. */
char hex[6]; /* Uppercase hex, e.g. "0x41". */
char oct[5]; /* Zero-padded 3-digit octal, e.g. "101". */
char binary[10]; /* Zero-padded 8-bit binary, e.g. "01000001". */
char char_glyph[5]; /* Display glyph (named `char` in the canonical sources); UTF-8 (≤ 3 bytes) + NUL. */
char name[40]; /* Human-readable name, e.g. "Line Feed (LF)". */
bool printable; /* True for 32–126 (visible or space). */
bool control; /* True for 0–31 and 127. */
char escape[3]; /* C-style escape token (e.g. "\\n"), or "" when there is none — C's stand-in for Option. */
} ascii_entry_t;
/*
* Official ASCII control-character names for 0–31 and 127.
* Expressed as a switch over every control code point; the default arm is a
* defensive guard that documents the precondition (the Rust port uses
* unreachable!() for the same case — C's nearest safe equivalent is "").
*/
static const char *control_name(int code)
{
switch (code) {
case 0: return "Null (NUL)";
case 1: return "Start of Heading (SOH)";
case 2: return "Start of Text (STX)";
case 3: return "End of Text (ETX)";
case 4: return "End of Transmission (EOT)";
case 5: return "Enquiry (ENQ)";
case 6: return "Acknowledge (ACK)";
case 7: return "Bell (BEL)";
case 8: return "Backspace (BS)";
case 9: return "Horizontal Tab (HT)";
case 10: return "Line Feed (LF)";
case 11: return "Vertical Tab (VT)";
case 12: return "Form Feed (FF)";
case 13: return "Carriage Return (CR)";
case 14: return "Shift Out (SO)";
case 15: return "Shift In (SI)";
case 16: return "Data Link Escape (DLE)";
case 17: return "Device Control 1 (DC1)";
case 18: return "Device Control 2 (DC2)";
case 19: return "Device Control 3 (DC3)";
case 20: return "Device Control 4 (DC4)";
case 21: return "Negative Acknowledge (NAK)";
case 22: return "Synchronous Idle (SYN)";
case 23: return "End of Transmission Block (ETB)";
case 24: return "Cancel (CAN)";
case 25: return "End of Medium (EM)";
case 26: return "Substitute (SUB)";
case 27: return "Escape (ESC)";
case 28: return "File Separator (FS)";
case 29: return "Group Separator (GS)";
case 30: return "Record Separator (RS)";
case 31: return "Unit Separator (US)";
case 127: return "Delete (DEL)";
default: return ""; /* unreachable with control codes; defensive guard */
}
}
/*
* C-style escape for the control chars that own a standard/common one.
* Returns the literal 2-character source token (backslash + letter), or NULL.
*/
static const char *control_escape(int code)
{
switch (code) {
case 0: return "\\0";
case 7: return "\\a";
case 8: return "\\b";
case 9: return "\\t";
case 10: return "\\n";
case 11: return "\\v";
case 12: return "\\f";
case 13: return "\\r";
case 27: return "\\e";
default: return NULL;
}
}
/*
* Name for a printable punctuation / symbol glyph, or NULL.
* Letters and digits are derived in letter_name().
*/
static const char *symbol_name(int code)
{
switch (code) {
case 32: return "Space";
case 33: return "Exclamation mark";
case 34: return "Quotation mark";
case 35: return "Number sign";
case 36: return "Dollar sign";
case 37: return "Percent sign";
case 38: return "Ampersand";
case 39: return "Apostrophe";
case 40: return "Left parenthesis";
case 41: return "Right parenthesis";
case 42: return "Asterisk";
case 43: return "Plus sign";
case 44: return "Comma";
case 45: return "Hyphen / Minus";
case 46: return "Full stop";
case 47: return "Slash";
case 58: return "Colon";
case 59: return "Semicolon";
case 60: return "Less-than sign";
case 61: return "Equals sign";
case 62: return "Greater-than sign";
case 63: return "Question mark";
case 64: return "At sign";
case 91: return "Left bracket";
case 92: return "Backslash";
case 93: return "Right bracket";
case 94: return "Circumflex / Caret";
case 95: return "Underscore";
case 96: return "Grave accent";
case 123: return "Left brace";
case 124: return "Vertical bar";
case 125: return "Right brace";
case 126: return "Tilde";
default: return NULL;
}
}
/* Derive a name for a printable letter or digit. */
static void letter_name(int code, char out[40])
{
if (code >= 48 && code <= 57) /* '0'..'9' */
snprintf(out, 40, "Digit %d", code - 48);
else if (code >= 65 && code <= 90) /* 'A'..'Z' */
snprintf(out, 40, "Uppercase %c", (char)code);
else if (code >= 97 && code <= 122) /* 'a'..'z' */
snprintf(out, 40, "Lowercase %c", (char)code);
else
snprintf(out, 40, "%c", (char)code);
}
/*
* Encode a Unicode scalar (U+0800..U+FFFF) as a 3-byte UTF-8 sequence + NUL.
* Every control-picture glyph used here (U+2400..U+2421) is in that range.
*/
static void utf8_encode3(unsigned cp, char out[4])
{
out[0] = (char)(0xE0u | (cp >> 12));
out[1] = (char)(0x80u | ((cp >> 6) & 0x3Fu));
out[2] = (char)(0x80u | (cp & 0x3Fu));
out[3] = '\0';
}
/* Zero-padded 8-digit binary (standard C11 has no "%b" conversion). */
static void fmt_binary(unsigned v, char out[10])
{
for (int i = 0; i < 8; i++)
out[i] = (char)('0' + ((v >> (7 - i)) & 1u));
out[8] = '\0';
}
/* Build a single entry from its code point (0–127). */
static ascii_entry_t make_entry(int code)
{
ascii_entry_t e;
const bool control = code <= 31 || code == 127;
e.dec = code;
snprintf(e.hex, sizeof e.hex, "0x%02X", code);
snprintf(e.oct, sizeof e.oct, "%03o", code);
fmt_binary((unsigned)code, e.binary);
/* Control-picture glyph: U+2400 for 0–31, U+2421 ("symbol for delete") for DEL. */
if (control) {
utf8_encode3(code == 127 ? 0x2421u : 0x2400u + (unsigned)code, e.char_glyph);
} else {
e.char_glyph[0] = (char)code;
e.char_glyph[1] = '\0';
}
if (control) {
const char *esc = control_escape(code);
snprintf(e.name, sizeof e.name, "%s", control_name(code));
snprintf(e.escape, sizeof e.escape, "%s", esc != NULL ? esc : "");
} else {
const char *sym = symbol_name(code);
if (sym != NULL)
snprintf(e.name, sizeof e.name, "%s", sym);
else
letter_name(code, e.name);
e.escape[0] = '\0';
}
e.printable = !control;
e.control = control;
return e;
}
/*
* The 128-entry table is built once on first use and never mutated afterwards.
* C11 has no portable call-once without <threads.h>; the first call is assumed
* single-threaded, and concurrent READS of the finished table are safe.
*/
static ascii_entry_t g_table[ASCII_TABLE_SIZE];
static bool g_ready = false;
static void ascii_init(void)
{
if (g_ready)
return;
for (int i = 0; i < ASCII_TABLE_SIZE; i++)
g_table[i] = make_entry(i);
g_ready = true;
}
/* The full 128-entry ASCII table, indexed by code point. */
const ascii_entry_t *ascii_entries(void)
{
ascii_init();
return g_table;
}
/*
* Look up a single entry by code point.
* Returns NULL for out-of-range input — the idiomatic C equivalent of the
* TypeScript variant's `null` and Rust's `Option`.
*/
const ascii_entry_t *ascii_get_entry(int code)
{
if (code < 0 || code > 127)
return NULL;
return &ascii_entries()[code];
}
static char lower_byte(char c)
{
return (char)tolower((unsigned char)c);
}
/*
* Case-insensitive substring test with explicit lengths: every haystack and
* needle byte is ASCII-lowered before comparison, which matches the reference
* implementations (they lowercase both sides).
*/
static bool ci_contains_n(const char *hay, size_t hlen, const char *needle, size_t nlen)
{
if (nlen == 0)
return true;
if (hlen < nlen)
return false;
for (size_t i = 0; i + nlen <= hlen; i++) {
size_t j = 0;
while (j < nlen && lower_byte(hay[i + j]) == lower_byte(needle[j]))
j++;
if (j == nlen)
return true;
}
return false;
}
/*
* Filter the table.
*
* `query` is a case-insensitive substring matched against dec / hex / oct /
* binary / name / char / escape; surrounding whitespace is trimmed.
* `printable_only` hides controls. `min` / `max` clamp the decimal range
* (defaults 0–127). Matching entries are stored (at most `cap` pointers) into
* `out` — pass NULL to just count. The number of matches is returned; when it
* exceeds `cap`, the excess entries are counted but not stored.
*/
size_t ascii_filter_table(const char *query, bool printable_only, int min, int max,
const ascii_entry_t *out[], size_t cap)
{
/* Trim surrounding whitespace; the needle length is tracked explicitly. */
const char *q = query != NULL ? query : "";
while (*q != '\0' && isspace((unsigned char)*q))
q++;
size_t qlen = strlen(q);
while (qlen > 0 && isspace((unsigned char)q[qlen - 1]))
qlen--;
size_t count = 0;
for (int i = 0; i < ASCII_TABLE_SIZE; i++) {
const ascii_entry_t *e = &ascii_entries()[i];
if (printable_only && !e->printable)
continue;
if (e->dec < min || e->dec > max)
continue;
if (qlen > 0) {
char dec[4];
snprintf(dec, sizeof dec, "%d", e->dec);
bool hit = ci_contains_n(dec, strlen(dec), q, qlen)
|| ci_contains_n(e->hex, strlen(e->hex), q, qlen)
|| ci_contains_n(e->oct, strlen(e->oct), q, qlen)
|| ci_contains_n(e->binary, strlen(e->binary), q, qlen)
|| ci_contains_n(e->name, strlen(e->name), q, qlen)
|| ci_contains_n(e->char_glyph, strlen(e->char_glyph), q, qlen)
|| ci_contains_n(e->escape, strlen(e->escape), q, qlen);
if (!hit)
continue;
}
if (out != NULL && count < cap)
out[count] = e;
count++;
}
return count;
}
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 →