unicode

Unicode in MoonBit

unicode
bidi
idna
nfc
nfd
nfkc
nfkd
punycode
moon add tonyfettes/unicode@0.4.0
Download zip
Version
0.4.0
License
Apache-2.0
Last updated
last month
Downloads
9K
README

#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 tonyfettes/ucd moon add tonyfettes/normalization moon add tonyfettes/punycode moon add tonyfettes/bidi moon add tonyfettes/idna

Import the packages you need in moon.pkg:

import {
"tonyfettes/ucd"
"tonyfettes/normalization"
"tonyfettes/punycode"
"tonyfettes/idna"
"tonyfettes/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 tonyfettes/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

#tonyfettes/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.

#tonyfettes/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.

#tonyfettes/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.

#tonyfettes/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.

#tonyfettes/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 tonyfettes/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 tonyfettes/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, then create and push the matching v0.4.0 tag. Publication is a separate, explicit step:

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

The tool publishes ucd, punycode, bidi, normalization, idna, and the unicode compatibility umbrella in dependency order. 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, resolve the problem and resume at the failed module, for example:

moon run --target native tools/release -- publish \ --version 0.4.0 \ --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