Skip to content

ASCII Table — PHP 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 PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.

<?php
/**
 * ASCII Table — pure logic (PHP port).
 *
 * CosmoDev polyglot showcase port of the `ascii-table` tool, ported from
 * src/lib/ascii-table.ts. Every output is a pure function of the code point
 * 0–127; no I/O, no Composer dependencies — PHP's standard library only.
 *
 * 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.
 */

declare(strict_types=1);

namespace CosmoDev\AsciiTable;

/**
 * A single ASCII code point and its derived representations.
 */
final class Entry
{
    public function __construct(
        public readonly int $dec,
        public readonly string $hex,
        public readonly string $oct,
        public readonly string $binary,
        public readonly string $char,
        public readonly string $name,
        public readonly bool $printable,
        public readonly bool $control,
        public readonly ?string $escape,
    ) {}
}

/**
 * Official ASCII control-character names (0–31 and 127).
 * `static` keeps the lookup table local yet initialized only once.
 */
function controlName(int $code): ?string
{
    static $names = [
        0   => 'Null (NUL)',
        1   => 'Start of Heading (SOH)',
        2   => 'Start of Text (STX)',
        3   => 'End of Text (ETX)',
        4   => 'End of Transmission (EOT)',
        5   => 'Enquiry (ENQ)',
        6   => 'Acknowledge (ACK)',
        7   => 'Bell (BEL)',
        8   => 'Backspace (BS)',
        9   => 'Horizontal Tab (HT)',
        10  => 'Line Feed (LF)',
        11  => 'Vertical Tab (VT)',
        12  => 'Form Feed (FF)',
        13  => 'Carriage Return (CR)',
        14  => 'Shift Out (SO)',
        15  => 'Shift In (SI)',
        16  => 'Data Link Escape (DLE)',
        17  => 'Device Control 1 (DC1)',
        18  => 'Device Control 2 (DC2)',
        19  => 'Device Control 3 (DC3)',
        20  => 'Device Control 4 (DC4)',
        21  => 'Negative Acknowledge (NAK)',
        22  => 'Synchronous Idle (SYN)',
        23  => 'End of Transmission Block (ETB)',
        24  => 'Cancel (CAN)',
        25  => 'End of Medium (EM)',
        26  => 'Substitute (SUB)',
        27  => 'Escape (ESC)',
        28  => 'File Separator (FS)',
        29  => 'Group Separator (GS)',
        30  => 'Record Separator (RS)',
        31  => 'Unit Separator (US)',
        127 => 'Delete (DEL)',
    ];
    return $names[$code] ?? null;
}

/**
 * C-style escape for the control chars that own a standard/common one.
 * Returns the literal 2-character source token (backslash + letter), or null.
 */
function controlEscape(int $code): ?string
{
    static $escapes = [
        0  => '\\0',
        7  => '\\a',
        8  => '\\b',
        9  => '\\t',
        10 => '\\n',
        11 => '\\v',
        12 => '\\f',
        13 => '\\r',
        27 => '\\e',
    ];
    return $escapes[$code] ?? null;
}

/**
 * Names for printable punctuation & symbols; letter and digit names are derived.
 */
function symbolName(int $code): ?string
{
    static $names = [
        32  => 'Space',
        33  => 'Exclamation mark',
        34  => 'Quotation mark',
        35  => 'Number sign',
        36  => 'Dollar sign',
        37  => 'Percent sign',
        38  => 'Ampersand',
        39  => 'Apostrophe',
        40  => 'Left parenthesis',
        41  => 'Right parenthesis',
        42  => 'Asterisk',
        43  => 'Plus sign',
        44  => 'Comma',
        45  => 'Hyphen / Minus',
        46  => 'Full stop',
        47  => 'Slash',
        58  => 'Colon',
        59  => 'Semicolon',
        60  => 'Less-than sign',
        61  => 'Equals sign',
        62  => 'Greater-than sign',
        63  => 'Question mark',
        64  => 'At sign',
        91  => 'Left bracket',
        92  => 'Backslash',
        93  => 'Right bracket',
        94  => 'Circumflex / Caret',
        95  => 'Underscore',
        96  => 'Grave accent',
        123 => 'Left brace',
        124 => 'Vertical bar',
        125 => 'Right brace',
        126 => 'Tilde',
    ];
    return $names[$code] ?? null;
}

/**
 * Encode a Unicode code point as raw UTF-8 bytes.
 *
 * PHP's chr() only emits a single byte, so the multi-byte control-picture
 * glyphs (U+2400–U+2421) cannot be produced with chr() alone. Rather than pull
 * in the optional mbstring/intl extensions, we implement the standard UTF-8
 * encoding directly — this keeps the snippet dependency-free.
 */
function utf8(int $codepoint): string
{
    if ($codepoint < 0x80) {
        return chr($codepoint);
    }
    if ($codepoint < 0x800) {
        return chr(0xC0 | ($codepoint >> 6))
             . chr(0x80 | ($codepoint & 0x3F));
    }
    if ($codepoint < 0x10000) {
        return chr(0xE0 | ($codepoint >> 12))
             . chr(0x80 | (($codepoint >> 6) & 0x3F))
             . chr(0x80 | ($codepoint & 0x3F));
    }
    return chr(0xF0 | ($codepoint >> 18))
         . chr(0x80 | (($codepoint >> 12) & 0x3F))
         . chr(0x80 | (($codepoint >> 6) & 0x3F))
         . chr(0x80 | ($codepoint & 0x3F));
}

/**
 * Derive a name for a printable letter or digit.
 */
function letterName(int $code): string
{
    if ($code >= 48 && $code <= 57) {
        return 'Digit ' . ($code - 48);                       // '0'..'9'
    }
    if ($code >= 65 && $code <= 90) {
        return 'Uppercase ' . chr($code);                      // 'A'..'Z'
    }
    if ($code >= 97 && $code <= 122) {
        return 'Lowercase ' . chr($code);                      // 'a'..'z'
    }
    return chr($code);
}

/**
 * Build a single entry from its code point (0–127).
 */
function makeEntry(int $code): Entry
{
    $control = $code <= 31 || $code === 127;

    // Control-picture glyph: U+2400 for 0–31, U+2421 ("symbol for delete") for DEL.
    if ($control) {
        $cp = $code === 127 ? 0x2421 : 0x2400 + $code;
        $char = utf8($cp);
    } else {
        $char = chr($code);
    }

    if ($control) {
        $name = controlName($code);
    } elseif (($sym = symbolName($code)) !== null) {
        $name = $sym;
    } else {
        $name = letterName($code);
    }

    $escape = $control ? controlEscape($code) : null;

    return new Entry(
        dec: $code,
        hex: '0x' . strtoupper(sprintf('%02x', $code)),
        oct: sprintf('%03o', $code),
        binary: sprintf('%08b', $code),
        char: $char,
        name: $name,
        printable: !$control,
        control: $control,
        escape: $escape,
    );
}

/**
 * The full 128-entry ASCII table, indexed by code point.
 *
 * Memoized via a static variable so the table is built only once per request.
 */
function entries(): array
{
    static $table = null;
    if ($table === null) {
        $table = array_map(makeEntry(...), range(0, 127));
    }
    return $table;
}

/**
 * Look up a single entry by code point.
 *
 * Returns null for out-of-range / non-integer input. PHP's is_int already
 * excludes floats, strings, and null; booleans are rejected because filterTable
 * callers may pass loose input.
 *
 * @param mixed $code
 */
function getEntry($code): ?Entry
{
    if (!is_int($code) || $code < 0 || $code > 127) {
        return null;
    }
    return entries()[$code];
}

/**
 * Filter the table.
 *
 * `$query` is a case-insensitive substring matched against dec / hex / oct /
 * binary / name / char / escape. `$printableOnly` hides controls. `$min` /
 * `$max` clamp the decimal range (defaults 0–127).
 *
 * `strtolower` is sufficient here — every cased haystack character is ASCII,
 * and the multi-byte control-picture glyphs have no case, so they pass through
 * unchanged. This avoids requiring the mbstring extension.
 */
function filterTable(array $opts = []): array
{
    $q = strtolower(trim($opts['query'] ?? ''));
    $lo = $opts['min'] ?? 0;
    $hi = $opts['max'] ?? 127;

    $matching = array_filter(entries(), function (Entry $e) use ($q, $lo, $hi, $opts): bool {
        if (!empty($opts['printableOnly']) && !$e->printable) {
            return false;
        }
        if ($e->dec < $lo || $e->dec > $hi) {
            return false;
        }
        if ($q === '') {
            return true;
        }
        $haystack = [
            (string) $e->dec,
            strtolower($e->hex),
            $e->oct,
            $e->binary,
            strtolower($e->name),
            strtolower($e->char),
            $e->escape ?? '',
        ];
        foreach ($haystack as $h) {
            if (str_contains($h, $q)) {
                return true;
            }
        }
        return false;
    });

    // array_values reindexes to a dense list, matching the TS Array.filter result.
    return array_values($matching);
}

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 →