// `Codepoint val` — typed wrapper around a validated Unicode scalar value.
//
// A `Codepoint` is constructed via `Codepoints.from_u32(u)` (validating) or
// `Codepoint._create(u)` (package-private, caller guarantees validity).
// The wrapped `U32` is always in the scalar-value range:
// 0x0000..0xD7FF or 0xE000..0x10FFFF — surrogates and out-of-range values
// can never appear in a `Codepoint`.
//
// For hot iteration (`Text.codepoints()`), users get bare `U32` instead —
// see design/candidate-v3.md §4.2 for the split-path rationale. `Codepoint`
// exists for the type-safe boundary case (constructing from external
// input, holding in a typed collection, etc.).
// Codepoint structurally satisfies Equatable[Codepoint], Comparable[Codepoint],
// Hashable, and Stringable via its methods below; no explicit `is` clause is
// needed (Pony interfaces are structural).
class val Codepoint
let _scalar: U32
new val _create(value: U32) =>
"""
Package-private constructor. Caller must have proved that `value`
is a Unicode scalar value (0x0000..0xD7FF or 0xE000..0x10FFFF).
Calling this with a surrogate or out-of-range value is a package
bug.
"""
_scalar = value
fun scalar(): U32 =>
"""
The underlying U32 scalar value.
"""
_scalar
fun category(): Category =>
"""
UAX #44 General Category of this codepoint.
"""
_UcdCategory.of(_scalar)
fun combining_class(): U8 =>
"""
Canonical Combining Class. Most codepoints return 0 (Not_Reordered);
only combining marks have non-zero values.
"""
_UcdCombiningClass.of(_scalar)
fun canonical_decomposition(): (Array[U32] val | None) =>
"""
Canonical Decomposition_Mapping per UAX #44. Returns `None` for
codepoints with no canonical decomposition (including Hangul
syllables — their decomposition is algorithmic).
"""
_UcdCanonicalDecomp.of(_scalar)
fun is_letter(): Bool =>
"""
True iff this codepoint's General Category is one of the Letter
categories: Lu, Ll, Lt, Lm, Lo.
"""
Codepoints._is_letter_category(category())
fun is_digit(): Bool =>
"""
True iff General Category is Decimal_Number (Nd). Letter-number
(Nl, e.g., Roman numerals) and other-number (No) are NOT
considered digits by this predicate.
"""
Codepoints._is_digit_category(category())
fun is_whitespace(): Bool =>
"""
True iff this codepoint is whitespace per a category-based
approximation: General Category in {Zs, Zl, Zp} plus a few
specific ASCII / Latin-1 control codes (HT, LF, VT, FF, CR, NEL).
NOTE: this is NOT the full UAX #44 `White_Space` property — the
full version requires the PropList.txt-derived table (lands with
M3b). For the common case of "is this an ASCII or Unicode space
separator," the category-based answer matches.
"""
Codepoints._is_whitespace_approx(_scalar, category())
fun is_assigned(): Bool =>
"""
True iff this codepoint is assigned in the bundled UCD — i.e.,
its General Category is anything other than `Cn` (Unassigned).
Use this to detect input that may contain codepoints from a
newer Unicode version than this package was built against.
"""
not (category() is Cn)
fun script(): Script =>
"""
The Script property of this codepoint per Scripts.txt. Returns
`ScriptUnknown` for codepoints with no assigned script.
"""
_UcdScript.of(_scalar)
fun script_extensions(): Array[Script] val =>
"""
The Script_Extensions property per UAX #24. Returns the set of
scripts that use this codepoint; for codepoints not listed in
ScriptExtensions.txt the result is `[script()]`.
"""
Codepoints.script_extensions(_scalar)
fun has_property(p: BinaryProperty): Bool =>
"""
True iff this codepoint has the binary property `p` per
PropList.txt, DerivedCoreProperties.txt, or emoji-data.txt.
"""
_UcdBinaryProps.has(_scalar, p)
fun east_asian_width(): EastAsianWidth =>
"""
The East_Asian_Width property of this codepoint per UAX #11.
Returns `EAWN` (Neutral) for codepoints with no explicit
assignment.
"""
_UcdEastAsianWidth.of(_scalar)
fun eq(that: Codepoint box): Bool =>
_scalar == that._scalar
fun lt(that: Codepoint box): Bool =>
_scalar < that._scalar
fun hash(): USize =>
_scalar.hash()
fun string(): String iso^ =>
"""
Hex representation in the form `U+XXXX` (with 4 or more hex digits
as needed). Suitable for debug output and error messages.
"""
let digits = "0123456789ABCDEF"
let out = recover iso String(8) end
out.append("U+")
var n: U32 = _scalar
let tmp = recover ref Array[U8](8) end
if n == 0 then tmp.push('0') end
while n > 0 do
try tmp.push(digits(USize.from[U32](n and 0xF))?) end
n = n >> 4
end
// Pad to at least 4 digits.
while tmp.size() < 4 do tmp.push('0') end
var i = tmp.size()
while i > 0 do
try out.push(tmp(i - 1)?) end
i = i - 1
end
consume out