iyi

Standard library, 0.18.0

std/unicode

The Unicode Character Database, as questions a program asks of a character and of a string: which general category a code point is in, what its upper, lower, title and folded case are, whether a byte sequence is well-formed UTF-8, how a string reads in UTF-16, and the four normalization forms.

import std/unicode to use it. Source: src/std/unicode.iyi at v0.18.0.

The tables between # tables-begin and # tables-end are written by scripts/generate_unicode_std.py from the UCD data the repository already carries (VERSION says which release) and, for Cased and Case_Ignorable, from DerivedCoreProperties.txt of the same release; they are not to be edited by hand. Each is a string of decimal integers decoded into an Array(Int32) the first time it is asked for, and every lookup is a binary search over rows of two, three or four integers.

What it answers, all under Unicode.:

* `letter?`, `number?`, `mark?`, `whitespace?`, `control?`, `lowercase?`,
  `uppercase?`, `titlecase?`, `ascii?` — the general categories, exact:
  `control?` is Cc, Cf, Cs and Co; `whitespace?` is Zs, Zl and Zp.
* `general_category` and `script` of a `Char` — `"Lu"`, `"Po"`, `"Cn"`
  for unassigned; `"Greek"`, `"Common"`, `"Unknown"` — and
  `property_ranges`, the code points of a category, a major class (`"P"`)
  or a script as sorted `{from, to}` pairs, which `std/regex` builds
  `\p{..}` from.
* `upcase`, `downcase`, `titlecase`, `foldcase` of a `Char` — the simple
  one-to-one mappings; with a block, the full mappings (`ß` upcases to
  `S`, `S`). `CaseOptions::ASCII` touches only ASCII, `Turkic` maps the
  dotted and dotless i, `Fold` makes `downcase` fold.
  `case_orbit` is every character with the same simple folding (`k`,
  `K` and the Kelvin sign), what a case-insensitive match accepts, and
  `each_case_orbit` yields every orbit of more than one.
* `upcase`, `downcase`, `capitalize`, `foldcase` of a `String` — the full
  mappings, and `downcase` writes the final sigma: a `Σ` that ends a word
  becomes `ς`.
* `valid?` and `clean` — whether bytes are well-formed UTF-8 (overlong
  forms, surrogates, code points past U+10FFFF and truncated sequences are
  not), and the same bytes with every maximal ill-formed subpart replaced
  by U+FFFD.
* `encode_utf16` and `decode_utf16` — UTF-16 code units as `Array(UInt16)`.
* `normalize` and `normalized?` — NFD, NFC, NFKD and NFKC, with canonical
  ordering by combining class and algorithmic Hangul.
* `graphemes`, `each_grapheme` and `grapheme_size` — the extended
  grapheme clusters of UAX #29, every rule through GB9c's Indic
  conjuncts, on `Unicode.` and on `String` itself: `"🇹🇷🇩🇪".graphemes` is
  two flags. A piece of ill-formed UTF-8 is a cluster of its own.

What it refuses: a string that is not well-formed UTF-8 is not case-mapped, encoded or normalized, it panics with the byte offset ("invalid UTF-8 at byte 3"); an unpaired surrogate in UTF-16 is nil from decode_utf16? and a panic from decode_utf16.

Unicodestruct

std/unicodeiyi doc src/std/unicode.iyi
pub struct Unicode
  # Options the case mappings take; combine them with `|`.
  enum CaseOptions : Int32
    ASCII = 1
    Turkic = 2
    Fold = 4
  end
  # The normalization forms `normalize` writes.
  enum NormalizationForm : Int32
    NFD = 0
    NFC = 1
    NFKD = 2
    NFKC = 3
  end
  def self.ascii?(char : Char) : Bool
  # The first character in titlecase and the rest in lowercase: `"džemal"` is
  # `"Džemal"`, `"STRASSE"` is `"Strasse"`. The first character is still a
  # letter before the rest, so `"ΑΣ"` is `"Ας"`.
  def self.capitalize(text : String, options : CaseOptions = CaseOptions::None) : String
  # Every character whose simple case folding is this one's, itself among
  # them, in code point order: `k` answers `K`, `k` and the Kelvin sign
  # U+212A, `σ` answers `Σ`, `ς` and `σ`, and `ß` answers `ß` and `ẞ`. A
  # character without case answers itself alone. It is what a
  # case-insensitive comparison of one character has to accept, and folding
  # both sides is not enough to build it: a class holding the Kelvin sign
  # has to take `K` as well, and only the folding read backwards says so.
  #
  # The simple folding here is CaseFolding.txt's C and S rows, which is
  # RE2's. `foldcase` answers the other library's, which has no S rows:
  # there `ẞ` folds to itself.
  def self.case_orbit(char : Char) : Array(Char)
  # The string with every maximal ill-formed subpart replaced by U+FFFD; a
  # well-formed string is answered as it is.
  def self.clean(text : String) : String
  # The bytes as a string, every maximal ill-formed subpart replaced by U+FFFD.
  def self.clean(bytes : Array(UInt8)) : String
  # Category C: Cc, Cf, Cs and Co — controls, format characters,
  # surrogates and private use.
  def self.control?(char : Char) : Bool
  # The string the UTF-16 code units spell. An unpaired surrogate is refused
  # with its index: `unpaired surrogate 0xD800 at index 3`.
  def self.decode_utf16(units : Array(UInt16)) : String
  # The string the UTF-16 code units spell, or nil at an unpaired surrogate.
  def self.decode_utf16?(units : Array(UInt16)) : String | ::Nil
  # The lowercase of one character, the simple mapping: `İ` becomes `i`
  # under Turkic rules and stays `İ` otherwise, because its full mapping is
  # `i` plus a combining dot.
  def self.downcase(char : Char, options : CaseOptions = CaseOptions::None) : Char
  # The full lowercase mapping, one character at a time.
  def self.downcase(char : Char, options : CaseOptions = CaseOptions::None, & : (Char -> Nil)) : Nil
  # The string in lowercase, full mappings, with the final sigma: a capital
  # sigma that ends a word — a cased letter before it, none after, the
  # case-ignorable characters (combining marks, format characters, modifier
  # letters and the apostrophes, periods and colons that can sit inside a
  # word) looked through — becomes `ς`, so `"ΟΔΥΣΣΕΥΣ"` is `"οδυσσευς"`.
  # With `CaseOptions::Fold` this is `foldcase`.
  def self.downcase(text : String, options : CaseOptions = CaseOptions::None) : String
  # Every orbit `case_orbit` answers that holds more than one character,
  # each once, as code points in order and in no particular order between
  # them. A case-insensitive class folds itself against these, about 1,400
  # of them, rather than asking `case_orbit` of each of the hundred
  # thousand code points a class like `\p{L}` holds.
  def self.each_case_orbit(& : (Array(Int32) -> Nil)) : Nil
  # Each grapheme cluster, in order.
  def self.each_grapheme(text : String, &block : (String -> _)) : Nil
  # The string's UTF-16 code units: one per character up to U+FFFF, a
  # surrogate pair above.
  def self.encode_utf16(text : String) : Array(UInt16)
  # The case folding of one character where it is one code point (`Σ`, `σ`
  # and `ς` all fold to `σ`); a character whose folding is longer, like `ß`,
  # is answered unchanged — the block form and the string form fold it.
  def self.foldcase(char : Char) : Char
  # The full case folding, one character at a time: `ß` yields `s` twice.
  def self.foldcase(char : Char, & : (Char -> Nil)) : Nil
  # The string folded for comparison: `"Straße"` and `"STRASSE"` both fold
  # to `"strasse"`.
  def self.foldcase(text : String) : String
  # The two-letter general category of a character: `"Lu"` for `A`, `"Po"`
  # for `!`, `"Sm"` for `+`; `"Cn"` for a code point no character is
  # assigned to.
  def self.general_category(char : Char) : String
  # How many grapheme clusters the string has: what a reader counts.
  def self.grapheme_size(text : String) : Int32
  # The string's extended grapheme clusters, as UAX #29 divides them: what
  # a reader sees as one character - `e` and a combining acute, a flag's
  # two regional indicators, a family emoji joined by ZWJs, a Devanagari
  # conjunct - is one string. A piece of ill-formed UTF-8 is a cluster of
  # its own, as the U+FFFD that stands for it would be.
  def self.graphemes(text : String) : Array(String)
  def initialize
  # Category L: Lu, Ll, Lt, Lm and Lo.
  def self.letter?(char : Char) : Bool
  # Category Ll.
  def self.lowercase?(char : Char) : Bool
  # Category M: Mn, Mc and Me — the combining marks.
  def self.mark?(char : Char) : Bool
  # The string in the given normalization form, NFC when none is named.
  def self.normalize(text : String, form : NormalizationForm = NormalizationForm::NFC) : String
  # Whether the string is already in the given form.
  def self.normalized?(text : String, form : NormalizationForm = NormalizationForm::NFC) : Bool
  # Category N: Nd, Nl and No — digits in every script, letter numbers, and
  # the fractions and superscripts.
  def self.number?(char : Char) : Bool
  # The code points a property names, as sorted `{from, to}` pairs that
  # neither overlap nor touch: a general category (`"Lu"`), a major class
  # by its letter (`"L"` is Lu, Ll, Lt, Lm and Lo; `"C"` is Cc, Cf, Cs and
  # Co, unassigned code points being in no category), or a script
  # (`"Greek"`). Nil for a name that is none of those. A class is built
  # from the ranges once rather than asking per character, which is what a
  # regex's `\p{..}` needs: ranges it can negate and case-fold.
  def self.property_ranges(name : String) : Array(Int32) | ::Nil
  # The script a character is written in, as `Scripts.txt` names it:
  # `"Latin"`, `"Greek"`, `"Han"`, and `"Common"` for the digits and
  # punctuation every script shares; `"Unknown"` for a code point the file
  # does not list.
  def self.script(char : Char) : String
  # The titlecase of one character, the simple mapping: the uppercase, except
  # for the digraphs, where `dž` and `DŽ` both become `Dž`.
  def self.titlecase(char : Char, options : CaseOptions = CaseOptions::None) : Char
  # The full titlecase mapping, one character at a time: `ß` yields `S`, `s`.
  def self.titlecase(char : Char, options : CaseOptions = CaseOptions::None, & : (Char -> Nil)) : Nil
  # Category Lt: the digraphs whose first half is capital, `Dž` and `ᾈ`.
  def self.titlecase?(char : Char) : Bool
  # The uppercase of one character: the simple mapping, so `ß` stays `ß`
  # (its full mapping is two letters; the block form gives it).
  def self.upcase(char : Char, options : CaseOptions = CaseOptions::None) : Char
  # The full uppercase mapping, one character at a time: `ß` yields `S` twice,
  # `ʼn` yields `ʼ` and `N`.
  def self.upcase(char : Char, options : CaseOptions = CaseOptions::None, & : (Char -> Nil)) : Nil
  # The string in uppercase, full mappings: `"straße"` is `"STRASSE"`.
  # An ASCII-only string is the prelude's `upcase`, unless the Turkic rule
  # is on, which is a question about `i` itself.
  def self.upcase(text : String, options : CaseOptions = CaseOptions::None) : String
  # Category Lu.
  def self.uppercase?(char : Char) : Bool
  # Whether the string's bytes are well-formed UTF-8.
  def self.valid?(text : String) : Bool
  # Whether the bytes are well-formed UTF-8.
  def self.valid?(bytes : Array(UInt8)) : Bool
  # Category Z: Zs, Zl and Zp — the spaces and the line and paragraph
  # separators. Tab and newline are controls, not whitespace, here.
  def self.whitespace?(char : Char) : Bool
end

Stringclass, reopened

std/unicodeiyi doc src/std/unicode.iyi
class ::String
  def each_grapheme(&block : (String -> _)) : Nil
  def grapheme_size : Int32
  def graphemes : Array(String)
end