unicode

    Unicode in MoonBit

    unicode
    bidi
    idna
    nfc
    nfd
    nfkc
    nfkd
    punycode
    Download zip
    Version
    0.5.0
    License
    Apache-2.0
    Last updated
    27 days ago
    Downloads
    8

    #unicode

    Unicode support for MoonBit.

    This library implements selected Unicode standards for normalization, case and category lookup, Punycode, IDNA domain processing, and bidirectional text. The generated tables target Unicode 16.0.0.

    #Features

    • Unicode Normalization Forms from UAX #15: NFD, NFC, NFKD, and NFKC
    • Punycode encoding and decoding from RFC 3492
    • IDNA processing from UTS #46, including mapping, validation, Bidi checks, joiner checks, and DNS length checks
    • Unicode Bidirectional Algorithm support from UAX #9
    • General_Category, XID_Start, and XID_Continue lookup plus simple/full case mapping from the Unicode Character Database

    #Installation

    Add only the feature modules your application needs:

    moon add moonbit-community/ucd moon add moonbit-community/normalization moon add moonbit-community/punycode moon add moonbit-community/bidi moon add moonbit-community/idna

    Import the packages you need in moon.pkg:

    import {
    "moonbit-community/ucd"
    "moonbit-community/normalization"
    "moonbit-community/punycode"
    "moonbit-community/idna"
    "moonbit-community/bidi"
    }

    MoonBit uses the last package path segment as the default alias, so these imports are used as @ucd, @normalization, @punycode, @idna, and @bidi. Existing users may continue depending on moonbit-community/unicode; it is an umbrella compatibility module that preserves the old package paths.

    #Usage

    #Normalization

    let composed = @normalization.nfc("e\u{0301}") // "é"
    let decomposed = @normalization.nfd("é") // "e" + combining acute
    let compatible = @normalization.nfkc("fi") // "fi"

    let already_nfc = @normalization.is_normalized(
    composed,
    @normalization.NFC,
    )

    let normalized = @normalization.normalize(
    "text",
    @normalization.NFKC,
    )

    #Punycode

    encode and decode use checked errors. Use try! for examples or handle the error explicitly in application code.

    let encoded = try! @punycode.encode("münchen") // "mnchen-3ya"
    let decoded = try! @punycode.decode("mnchen-3ya") // "münchen"

    let chinese = try! @punycode.encode("中文") // "fiq228c"

    #IDNA

    let ascii = try! @idna.to_ascii("münchen.de")
    // "xn--mnchen-3ya.de"

    let unicode = try! @idna.to_unicode("xn--mnchen-3ya.de")
    // "münchen.de"

    let checked = try! @idna.to_ascii(
    "example.com",
    use_std3_ascii_rules=true,
    check_hyphens=true,
    check_bidi=true,
    check_joiners=true,
    verify_dns_length=true,
    )

    To handle validation failures:

    try @idna.to_ascii("example..com") catch {
    err => println("invalid domain: \{err}")
    } noraise {
    ascii => println(ascii)
    }

    #Bidi

    let direction = @bidi.detect_direction("Hello World") // LTR
    let needs_bidi = @bidi.requires_bidi("Hello \u{05E9}\u{05DC}\u{05D5}\u{05DD}")

    let paragraph = @bidi.process("abc\u{05D0}\u{05D1}")
    let visual = @bidi.reorder_string(paragraph)
    let order = @bidi.reorder(paragraph)

    let forced = @bidi.process(
    "\u{05D0}\u{05D1}\u{05D2}",
    direction=@bidi.Direction::LTR,
    )

    #Case And Category Data

    let category = @ucd.general_category('A') // Lu
    let group = category.group() // L
    let can_start = @ucd.is_xid_start('A') // true
    let can_continue = @ucd.is_xid_continue('0') // true

    let simple = @ucd.to_simple_uppercase('a') // 'A'
    let full = @ucd.to_uppercase('\u{00DF}') // "SS"
    let lower = @ucd.to_lowercase('\u{0130}') // "i" + combining dot above

    #Public Packages

    #moonbit-community/ucd

    Root package for Unicode Character Database helpers.

    APIDescription
    general_category(Char) -> GeneralCategoryReturn the two-letter Unicode General_Category value.
    GeneralCategory::group() -> GeneralCategoryGroupReturn the one-letter category group.
    is_xid_start(Char) -> BoolTest the Unicode XID_Start identifier property.
    is_xid_continue(Char) -> BoolTest the Unicode XID_Continue identifier property.
    to_simple_uppercase(Char) -> CharSimple uppercase mapping.
    to_simple_lowercase(Char) -> CharSimple lowercase mapping.
    to_simple_titlecase(Char) -> CharSimple titlecase mapping.
    to_uppercase(Char) -> StringFull uppercase mapping.
    to_lowercase(Char) -> StringFull lowercase mapping.
    to_titlecase(Char) -> StringFull titlecase mapping.

    #moonbit-community/normalization

    APIDescription
    nfd(String) -> StringCanonical decomposition.
    nfc(String) -> StringCanonical decomposition followed by canonical composition.
    nfkd(String) -> StringCompatibility decomposition.
    nfkc(String) -> StringCompatibility decomposition followed by canonical composition.
    normalize(String, NormalizationForm) -> StringNormalize with a selected form.
    is_normalized(String, NormalizationForm) -> BoolCheck whether text is already in a selected form.

    The available forms are NFD, NFC, NFKD, and NFKC.

    #moonbit-community/punycode

    APIDescription
    encode(String) -> String raise PunycodeErrorEncode Unicode text as Punycode.
    decode(String) -> String raise PunycodeErrorDecode Punycode text back to Unicode.

    PunycodeError variants are Overflow, InvalidInput, and BadInput.

    #moonbit-community/idna

    APIDescription
    to_ascii(String, ...) -> String raise IdnaErrorConvert a domain name to ASCII form for DNS use.
    to_unicode(String, ...) -> String raise IdnaErrorConvert an ASCII or ACE domain name to Unicode form for display.

    to_ascii accepts these optional checks, all defaulting to true:

    • use_std3_ascii_rules? : Bool
    • check_hyphens? : Bool
    • check_bidi? : Bool
    • check_joiners? : Bool
    • verify_dns_length? : Bool

    to_unicode accepts the same options except verify_dns_length; they also default to true.

    #moonbit-community/bidi

    APIDescription
    detect_direction(String) -> DirectionDetect the base direction from the first strong character.
    requires_bidi(String) -> BoolCheck whether text contains right-to-left characters.
    process(String, direction? : Direction) -> BidiParagraphResolve classes and levels, optionally forcing the base direction.
    reorder(BidiParagraph) -> Array[Int]Return visual-order indexes.
    reorder_string(BidiParagraph) -> StringReturn visually reordered text.
    bidi_class(Char) -> BidiClassReturn the Unicode Bidi_Class value.
    get_mirrored(Char, Int) -> CharReturn the mirrored character at an RTL level when one exists.
    direction_from_level(Int) -> DirectionConvert an embedding level to LTR or RTL.

    process_with_direction and process_with_base_level remain available as deprecated compatibility wrappers.

    #Development

    Common commands:

    moon check moon test moon test -p moonbit-community/normalization moon fmt moon info moon build

    Run moon info after public API changes to refresh pkg.generated.mbti files.

    Unicode data and conformance tests are generated from official Unicode files:

    moon run --target native tools/gen data moon run --target native tools/gen tests # Or regenerate everything in one pass: moon run --target native tools/gen all

    Individual commands are ucd, idna, bidi, normalization-tests, idna-tests, and bidi-tests. To control the number of Bidi conformance cases per generated package (default: 500), run moon run --target native tools/gen -- bidi-tests --part-size N. Downloaded Unicode source files are cached in tools/.cache/.

    Large generated conformance fixtures live in the workspace-only moonbit-community/unicode-conformance module, so they remain part of local and CI tests without inflating published feature archives. Verify a feature module's contents from its directory, for example:

    moon -C bidi package --list

    #Releasing

    The native release tool keeps the six published module versions aligned, validates the workspace, and checks every package archive. During preparation it dry-runs the independent ucd, punycode, and bidi modules:

    moon run --target native tools/release -- prepare --version 0.4.0

    Review and commit the resulting manifest changes to the default branch, then wait for CI to pass. Publication is a separate, explicit step that does not require a Git tag. Run the publish-package workflow in GitHub Actions, or run the underlying command from a clean checkout of the commit to publish:

    moon run --target native tools/release -- publish \ --execute

    The tool publishes ucd, punycode, bidi, normalization, idna, and the unicode compatibility umbrella in dependency order. It reads the release version from the root moon.mod and verifies that all six published modules use that version. Modules that depend on same-release packages are dry-run immediately before their real publication, after the tool refreshes the registry and their dependencies are available. It never publishes the workspace-only conformance or tools modules. If publication stops partway through, rerun the workflow with its from input set to the failed module, or resume with the underlying command:

    moon run --target native tools/release -- publish \ --from normalization \ --execute

    #Standards

    #License

    Apache-2.0. See LICENSE.

    GeneralCategory

    pub(all) enum GeneralCategory {
    Lu
    Ll
    Lt
    Lm
    Lo
    Mn
    Mc
    Me
    Nd
    Nl
    No
    Zs
    Zl
    Zp
    Pc
    Pd
    Ps
    Pe
    Pi
    Pf
    Po
    Sm
    Sc
    Sk
    So
    Cc
    Cf
    Cs
    Co
    Cn
    } derive(Eq)

    GeneralCategory::group

    GeneralCategoryGroup

    pub(all) enum GeneralCategoryGroup {
    L
    M
    N
    Z
    P
    S
    C
    } derive(Eq,
    Debug
    )

    general_category

    fn general_category(c : Char) -> GeneralCategory

    is_xid_continue

    fn is_xid_continue(c : Char) -> Bool

    Check whether a character has the Unicode XID_Continue property.

    XID_Continue is the recommended continuation class for Unicode default identifiers and includes every XID_Start character.

    is_xid_start

    fn is_xid_start(c : Char) -> Bool

    Check whether a character has the Unicode XID_Start property.

    XID_Start is the recommended start class for Unicode default identifiers.

    to_lowercase

    fn to_lowercase(c : Char) -> String

    Get the full lowercase mapping for a character Returns a String containing one or more lowercase characters For example, 'İ' (U+0130) maps to "i\u{0307}" (i with combining dot above)

    to_simple_lowercase

    fn to_simple_lowercase(c : Char) -> Char

    Get the simple lowercase mapping for a character Returns the character itself if no mapping exists

    to_simple_titlecase

    fn to_simple_titlecase(c : Char) -> Char

    Get the simple titlecase mapping for a character Returns the character itself if no mapping exists

    to_simple_uppercase

    fn to_simple_uppercase(c : Char) -> Char

    Get the simple uppercase mapping for a character Returns the character itself if no mapping exists

    to_titlecase

    fn to_titlecase(c : Char) -> String

    Get the full titlecase mapping for a character Returns a String containing one or more titlecase characters For example, 'ß' (U+00DF) maps to "Ss", and 'ffi' (U+FB03) maps to "Ffi"

    to_uppercase

    fn to_uppercase(c : Char) -> String

    Get the full uppercase mapping for a character Returns a String containing one or more uppercase characters For example, 'ß' (U+00DF) maps to "SS", and 'ffi' (U+FB03) maps to "FFI"

    Source Files