Skip to content

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

# frozen_string_literal: true

# ASCII Table — pure logic (Ruby port): the 128-entry ASCII reference table with
# search and range filters.
#
# Language: Ruby 3.2 — standard library only (core classes; no gems).
# 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.

module AsciiTable
  # A single ASCII code point and its derived representations.
  #
  #   dec       — Integer, decimal code point, 0–127.
  #   hex       — String, uppercase hex, e.g. "0x41".
  #   oct       — String, zero-padded 3-digit octal, e.g. "101".
  #   binary    — String, zero-padded 8-bit binary, e.g. "01000001".
  #   char      — String, display glyph: the literal char for printables, a
  #               control-picture glyph otherwise.
  #   name      — String, human-readable name, e.g. "Line Feed (LF)".
  #   printable — true for 32–126 (visible or space).
  #   control   — true for 0–31 and 127.
  #   escape    — String, C-style escape for control chars that have one
  #               (e.g. "\\n"); nil otherwise.
  Entry = Struct.new(
    :dec, :hex, :oct, :binary, :char, :name, :printable, :control, :escape,
    keyword_init: true
  )

  # Official ASCII control-character names (0–31 and 127).
  CONTROL_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)"
  }.freeze

  # C-style escape sequences for the control chars that have a standard/common
  # one. Each value is the literal 2-character source token (backslash + letter).
  ESCAPES = {
    0 => "\\0",
    7 => "\\a",
    8 => "\\b",
    9 => "\\t",
    10 => "\\n",
    11 => "\\v",
    12 => "\\f",
    13 => "\\r",
    27 => "\\e"
  }.freeze

  # Names for printable punctuation & symbols; letter and digit names are derived.
  SYMBOL_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"
  }.freeze

  class << self
    # Derive a name for a printable letter or digit.
    def letter_name(code)
      case code
      when 48..57  then "Digit #{code - 48}"        # '0'..'9'
      when 65..90  then "Uppercase #{code.chr}"     # 'A'..'Z'
      when 97..122 then "Lowercase #{code.chr}"     # 'a'..'z'
      else code.chr
      end
    end

    # Build a single entry from its code point (0–127).
    def make_entry(code)
      control = code <= 31 || code == 127

      # Control-picture glyph: U+2400 for 0–31, U+2421 ("symbol for delete") for DEL.
      char = if control
               (code == 127 ? 0x2421 : 0x2400 + code).chr(Encoding::UTF_8)
             else
               code.chr(Encoding::UTF_8)
             end

      name = if control
               CONTROL_NAMES.fetch(code)
             else
               SYMBOL_NAMES.fetch(code) { letter_name(code) }
             end

      Entry.new(
        dec: code,
        hex: format("0x%02X", code),
        oct: format("%03o", code),
        binary: format("%08b", code),
        char: char,
        name: name,
        printable: !control,
        control: control,
        escape: control ? ESCAPES[code] : nil
      )
    end

    # The full 128-entry ASCII table, indexed by code point.
    #
    # Built once and cached for the process lifetime. The array is frozen and
    # every entry is immutable by convention, so sharing it is safe for all
    # callers (the Ruby equivalent of Rust's OnceLock).
    def entries
      @entries ||= Array.new(128) { |i| make_entry(i) }.freeze
    end

    # Look up a single entry by code point.
    #
    # Returns nil for out-of-range or non-Integer input. Ruby's true/false are
    # not Integers, so the explicit boolean rejection the Python port needs is
    # unnecessary here — Integer#is_a? already excludes them.
    def get_entry(code)
      return nil unless code.is_a?(Integer)
      return nil unless code.between?(0, 127)

      entries[code]
    end

    # 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).
    def filter_table(query: "", printable_only: false, min: 0, max: 127)
      q = query.to_s.strip.downcase
      entries.select do |e|
        next false if printable_only && !e.printable
        next false if e.dec < min || e.dec > max
        next true if q.empty?

        haystacks = [
          e.dec.to_s, e.hex.downcase, e.oct, e.binary,
          e.name.downcase, e.char.downcase, e.escape.to_s
        ]
        haystacks.any? { |h| h.include?(q) }
      end
    end
  end
end

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 →