Category

// Unicode General Category (UAX #44).
//
// The 30 standard categories grouped by major class. The `Category` type is
// a closed union — exhaustive `match` is compile-checked; a new Unicode
// version that adds a category is a semver-significant breaking change
// (by design; see design/candidate-v3.md §10 improvement 3).
//
// Each category primitive's `code()` method returns its two-letter
// identifier (e.g., `"Lu"`, `"Mn"`). Categories implement `Stringable`
// so `.string()` returns the same.

// Letter
primitive Lu  // Uppercase Letter
  fun code(): String val => "Lu"
  fun string(): String val => "Lu"

primitive Ll  // Lowercase Letter
  fun code(): String val => "Ll"
  fun string(): String val => "Ll"

primitive Lt  // Titlecase Letter
  fun code(): String val => "Lt"
  fun string(): String val => "Lt"

primitive Lm  // Modifier Letter
  fun code(): String val => "Lm"
  fun string(): String val => "Lm"

primitive Lo  // Other Letter
  fun code(): String val => "Lo"
  fun string(): String val => "Lo"

// Mark
primitive Mn  // Nonspacing Mark
  fun code(): String val => "Mn"
  fun string(): String val => "Mn"

primitive Mc  // Spacing Mark
  fun code(): String val => "Mc"
  fun string(): String val => "Mc"

primitive Me  // Enclosing Mark
  fun code(): String val => "Me"
  fun string(): String val => "Me"

// Number
primitive Nd  // Decimal Number
  fun code(): String val => "Nd"
  fun string(): String val => "Nd"

primitive Nl  // Letter Number
  fun code(): String val => "Nl"
  fun string(): String val => "Nl"

primitive No  // Other Number
  fun code(): String val => "No"
  fun string(): String val => "No"

// Punctuation
primitive Pc  // Connector Punctuation
  fun code(): String val => "Pc"
  fun string(): String val => "Pc"

primitive Pd  // Dash Punctuation
  fun code(): String val => "Pd"
  fun string(): String val => "Pd"

primitive Ps  // Open Punctuation
  fun code(): String val => "Ps"
  fun string(): String val => "Ps"

primitive Pe  // Close Punctuation
  fun code(): String val => "Pe"
  fun string(): String val => "Pe"

primitive Pi  // Initial Punctuation
  fun code(): String val => "Pi"
  fun string(): String val => "Pi"

primitive Pf  // Final Punctuation
  fun code(): String val => "Pf"
  fun string(): String val => "Pf"

primitive Po  // Other Punctuation
  fun code(): String val => "Po"
  fun string(): String val => "Po"

// Symbol
primitive Sm  // Math Symbol
  fun code(): String val => "Sm"
  fun string(): String val => "Sm"

primitive Sc  // Currency Symbol
  fun code(): String val => "Sc"
  fun string(): String val => "Sc"

primitive Sk  // Modifier Symbol
  fun code(): String val => "Sk"
  fun string(): String val => "Sk"

primitive So  // Other Symbol
  fun code(): String val => "So"
  fun string(): String val => "So"

// Separator
primitive Zs  // Space Separator
  fun code(): String val => "Zs"
  fun string(): String val => "Zs"

primitive Zl  // Line Separator
  fun code(): String val => "Zl"
  fun string(): String val => "Zl"

primitive Zp  // Paragraph Separator
  fun code(): String val => "Zp"
  fun string(): String val => "Zp"

// Other
primitive Cc  // Control
  fun code(): String val => "Cc"
  fun string(): String val => "Cc"

primitive Cf  // Format
  fun code(): String val => "Cf"
  fun string(): String val => "Cf"

primitive Cs  // Surrogate
  fun code(): String val => "Cs"
  fun string(): String val => "Cs"

primitive Co  // Private Use
  fun code(): String val => "Co"
  fun string(): String val => "Co"

primitive Cn  // Unassigned (also used for "I don't know")
  fun code(): String val => "Cn"
  fun string(): String val => "Cn"

type Category is
  ( Lu | Ll | Lt | Lm | Lo
  | Mn | Mc | Me
  | Nd | Nl | No
  | Pc | Pd | Ps | Pe | Pi | Pf | Po
  | Sm | Sc | Sk | So
  | Zs | Zl | Zp
  | Cc | Cf | Cs | Co | Cn
  )

primitive Categories
  """
  Operations on the Category union. The `_all` array is generated by
  `unicode-build` alongside the rest of the closed-union enumeration
  arrays — tests can iterate all 30 variants without hand-maintaining
  the list (see design/candidate-v3.md Adj-23).
  """
  fun from_iso(s: String box): (Category | None) =>
    """
    Look up a category by its two-letter ISO identifier (case-sensitive).
    Returns `None` for unrecognized input. Total over its input.
    """
    match s
    | "Lu" => Lu | "Ll" => Ll | "Lt" => Lt | "Lm" => Lm | "Lo" => Lo
    | "Mn" => Mn | "Mc" => Mc | "Me" => Me
    | "Nd" => Nd | "Nl" => Nl | "No" => No
    | "Pc" => Pc | "Pd" => Pd | "Ps" => Ps | "Pe" => Pe
    | "Pi" => Pi | "Pf" => Pf | "Po" => Po
    | "Sm" => Sm | "Sc" => Sc | "Sk" => Sk | "So" => So
    | "Zs" => Zs | "Zl" => Zl | "Zp" => Zp
    | "Cc" => Cc | "Cf" => Cf | "Cs" => Cs | "Co" => Co | "Cn" => Cn
    else
      None
    end

  fun _to_byte(c: Category): U8 =>
    """
    Stable byte encoding for the 30 Category variants. Used by
    `unicode-build` and by the generated lookup tables to keep the
    encoding compact (one byte per codepoint range entry). The exact
    mapping is an implementation detail — callers should never see
    these bytes directly.
    """
    match c
    | Lu => 0  | Ll => 1  | Lt => 2  | Lm => 3  | Lo => 4
    | Mn => 5  | Mc => 6  | Me => 7
    | Nd => 8  | Nl => 9  | No => 10
    | Pc => 11 | Pd => 12 | Ps => 13 | Pe => 14
    | Pi => 15 | Pf => 16 | Po => 17
    | Sm => 18 | Sc => 19 | Sk => 20 | So => 21
    | Zs => 22 | Zl => 23 | Zp => 24
    | Cc => 25 | Cf => 26 | Cs => 27 | Co => 28 | Cn => 29
    end

  fun _from_byte(b: U8): Category =>
    """
    Inverse of `_to_byte`. Unknown bytes return `Cn` (Unassigned) — this
    is also the failure case for malformed generated tables, which would
    indicate a codegen / runtime version mismatch.
    """
    match b
    | 0  => Lu | 1  => Ll | 2  => Lt | 3  => Lm | 4  => Lo
    | 5  => Mn | 6  => Mc | 7  => Me
    | 8  => Nd | 9  => Nl | 10 => No
    | 11 => Pc | 12 => Pd | 13 => Ps | 14 => Pe
    | 15 => Pi | 16 => Pf | 17 => Po
    | 18 => Sm | 19 => Sc | 20 => Sk | 21 => So
    | 22 => Zs | 23 => Zl | 24 => Zp
    | 25 => Cc | 26 => Cf | 27 => Cs | 28 => Co | 29 => Cn
    else
      Cn
    end