Skip to content

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: C++17 — standard library only.
// 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 <algorithm>
#include <cctype>
#include <cstdint>
#include <iomanip>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>

namespace ascii_table {

/// A single ASCII code point and its derived representations.
struct Entry {
    /// Decimal code point, 0–127.
    std::uint8_t dec;
    /// Uppercase hex, e.g. "0x41".
    std::string hex;
    /// Zero-padded 3-digit octal, e.g. "101".
    std::string oct;
    /// Zero-padded 8-bit binary, e.g. "01000001".
    std::string binary;
    /// Display glyph: the literal char for printables, a control-picture glyph
    /// otherwise. (Named `char` in the canonical sources; that is a keyword here.)
    std::string char_glyph;
    /// Human-readable name, e.g. "Line Feed (LF)", "Uppercase A", "Space".
    std::string name;
    /// True for 32–126 (visible or space).
    bool printable;
    /// True for 0–31 and 127.
    bool control;
    /// C-style escape for control chars that have one (e.g. "\n"); nullopt otherwise.
    std::optional<std::string> escape;
};

namespace {

/// Uppercase hex with the 0x prefix, zero-padded to two digits.
std::string format_hex(std::uint8_t code) {
    std::ostringstream os;
    os << "0x" << std::uppercase << std::hex
       << std::setw(2) << std::setfill('0') << static_cast<int>(code);
    return os.str();
}

/// Zero-padded 3-digit octal (the standard streams have no std::bin-style
/// formatter for octal padding beyond setw/setfill, which is what this uses).
std::string format_oct(std::uint8_t code) {
    std::ostringstream os;
    os << std::oct << std::setw(3) << std::setfill('0') << static_cast<int>(code);
    return os.str();
}

/// Zero-padded 8-digit binary (std::format is C++20, so shift bits by hand).
std::string format_binary(std::uint8_t code) {
    std::string s(8, '0');
    for (int i = 0; i < 8; ++i)
        s[7 - i] = static_cast<char>('0' + ((code >> i) & 1));
    return s;
}

/// Encode a Unicode scalar (U+0800..U+FFFF) as a 3-byte UTF-8 sequence.
/// Every control-picture glyph used here (U+2400..U+2421) is in that range.
std::string utf8_encode3(unsigned cp) {
    std::string s(3, '\0');
    s[0] = static_cast<char>(0xE0u | (cp >> 12));
    s[1] = static_cast<char>(0x80u | ((cp >> 6) & 0x3Fu));
    s[2] = static_cast<char>(0x80u | (cp & 0x3Fu));
    return s;
}

/// 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).
std::string_view control_name(std::uint8_t 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 nullopt.
std::optional<std::string_view> control_escape(std::uint8_t 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 std::nullopt;
    }
}

/// Name for a printable punctuation / symbol glyph, or nullopt.
/// Letters and digits are derived in letter_name().
std::optional<std::string_view> symbol_name(std::uint8_t 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 std::nullopt;
    }
}

/// Derive a name for a printable letter or digit.
std::string letter_name(std::uint8_t code) {
    if (code >= 48 && code <= 57)       // '0'..'9'
        return "Digit " + std::to_string(code - 48);
    if (code >= 65 && code <= 90)       // 'A'..'Z'
        return std::string("Uppercase ") + static_cast<char>(code);
    if (code >= 97 && code <= 122)      // 'a'..'z'
        return std::string("Lowercase ") + static_cast<char>(code);
    return std::string(1, static_cast<char>(code));
}

/// Build a single entry from its code point (0–127).
Entry make_entry(std::uint8_t code) {
    const bool control = code <= 31 || code == 127;

    // Control-picture glyph: U+2400 for 0–31, U+2421 ("symbol for delete") for DEL.
    const std::string char_glyph =
        control ? utf8_encode3(code == 127 ? 0x2421u : 0x2400u + code)
                : std::string(1, static_cast<char>(code));

    std::string name;
    if (control) {
        name = std::string(control_name(code));
    } else if (auto sym = symbol_name(code)) {
        name = std::string(*sym);
    } else {
        name = letter_name(code);
    }

    return Entry{
        code,
        format_hex(code),
        format_oct(code),
        format_binary(code),
        char_glyph,
        name,
        !control,
        control,
        control ? std::optional<std::string>(std::string(*control_escape(code)))
                : std::nullopt,
    };
}

/// Case-insensitive substring test over ASCII text: both sides are lowered
/// byte-for-byte, matching the reference implementations.
bool ci_contains(std::string_view haystack, std::string_view needle) {
    const auto lower = [](char c) {
        return static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
    };
    return std::search(haystack.begin(), haystack.end(),
                       needle.begin(), needle.end(),
                       [&](char a, char b) { return lower(a) == lower(b); }) != haystack.end();
}

} // namespace

// The 128-entry table is built once and cached for the lifetime of the
// program. A function-local static is initialized exactly once, thread-safely
// ([stmt.dcl]/4) — the standard-library equivalent of Rust's OnceLock.
const std::vector<Entry>& entries() {
    static const std::vector<Entry> table = [] {
        std::vector<Entry> v;
        v.reserve(128);
        for (unsigned i = 0; i <= 127; ++i)
            v.push_back(make_entry(static_cast<std::uint8_t>(i)));
        return v;
    }();
    return table;
}

/// Look up a single entry by code point.
///
/// Returns a pointer into the shared static table, or nullptr for out-of-range
/// input — the idiomatic C++ equivalent of Rust's Option<&Entry> and the
/// TypeScript variant's null.
const Entry* get_entry(int code) {
    if (code < 0 || code > 127)
        return nullptr;
    return &entries()[static_cast<std::size_t>(code)];
}

/// Filter the table.
///
/// `query` is a case-insensitive substring matched against dec / hex / oct /
/// binary / name / char / escape. `printable_only` hides controls. `min` /
/// `max` clamp the decimal range (defaults 0–127). Returns pointers into the
/// shared static table, so filtering allocates nothing beyond the result vector.
std::vector<const Entry*> filter_table(const std::string& query = "",
                                       bool printable_only = false,
                                       int min = 0, int max = 127) {
    // Trim surrounding whitespace (string_view over the original string).
    std::string_view q = query;
    while (!q.empty() && std::isspace(static_cast<unsigned char>(q.front())))
        q.remove_prefix(1);
    while (!q.empty() && std::isspace(static_cast<unsigned char>(q.back())))
        q.remove_suffix(1);

    std::vector<const Entry*> out;
    for (const Entry& e : entries()) {
        if (printable_only && !e.printable)
            continue;
        if (e.dec < min || e.dec > max)
            continue;
        if (!q.empty()) {
            const std::string dec = std::to_string(e.dec);
            const std::string esc = e.escape ? *e.escape : "";
            const bool hit = ci_contains(dec, q) || ci_contains(e.hex, q) ||
                             ci_contains(e.oct, q) || ci_contains(e.binary, q) ||
                             ci_contains(e.name, q) || ci_contains(e.char_glyph, q) ||
                             ci_contains(esc, q);
            if (!hit)
                continue;
        }
        out.push_back(&e);
    }
    return out;
}

} // namespace ascii_table

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 →