flix

0.78.0

CodePoint.flix

/*
 * Copyright 2023 Stephen Tetley
 *
 * Use of this source code is governed by the Apache 2.0 license
 * that can be found in the LICENSE.md file.
 */

///
/// Functions for working with Unicode code points.
///
/// The `CodePoint` module complements the `Char` module. `Char` only
/// represents characters in Unicode's _Basic Multilingual Plane_
/// (U+0000 to U+FFFF).
/// CodePoint extends access to include _supplementary characters_ so all
/// Unicode characters can be represented.
///
pub mod CodePoint {

    import java.lang.Character
    import java.lang.IllegalArgumentException

    ///
    /// Returns the minimum integer value of a Unicode code point.
    ///
    pub def minValue(): Int32 = Character.MIN_CODE_POINT

    ///
    /// Returns the maximum integer value of a Unicode code point.
    ///
    pub def maxValue(): Int32 = Character.MAX_CODE_POINT

    ///
    /// Returns `true` if the given char `c` is an ascii character.
    ///
    pub def isAscii(cp: Int32): Bool =
        0x0000 <= cp and cp <= 0x0080

    ///
    /// Returns `true` if the given code point `cp` represents a letter character.
    ///
    pub def isLetter(cp: Int32): Bool =
        Character.isLetter(cp)

    ///
    /// Returns `true` if the given code point `cp` represents a recognized Unicode digit.
    /// This includes the ASCII range 0..9 but also Arabic-Indic digits, Devagari digits and Fullwidth digits.
    ///
    pub def isDigit(cp: Int32): Bool =
        Character.isDigit(cp)

    ///
    /// Returns `true` if the given code point `cp` represents a recognized Unicode letter or digit.
    ///
    pub def isLetterOrDigit(cp: Int32): Bool =
        Character.isLetterOrDigit(cp)

    ///
    /// Returns `true` if the given code point `cp` represents a character strictly in the
    /// range of ASCII digits 0...9.
    ///
    pub def isAsciiDigit(cp: Int32): Bool =
        isAscii(cp) and isDigit(cp)

    ///
    /// Returns `true` if the given code point `cp` represents a character in the range 0...7.
    ///
    pub def isOctDigit(cp: Int32): Bool =
        0x0030 <= cp and cp <= 0x0037 // '0'..'7'

    ///
    /// Returns `true` if the given code point `cp` represents a character in the range 0...F.
    ///
    pub def isHexDigit(cp: Int32): Bool = match cp {
        case i if 0x0030 <= i and i <= 0x0039 => true // '0'..'9'
        case i if 0x0041 <= i and i <= 0x0046 => true // 'A'..'F'
        case i if 0x0061 <= i and i <= 0x0066 => true // 'a'..'f'
        case _                                => false
    }

    ///
    /// Returns `true` if the given code point `cp` represents a lowercase letter.
    ///
    pub def isLowerCase(cp: Int32): Bool =
        Character.isLowerCase(cp)

    ///
    /// Returns `true` if the given code point `cp` represents an uppercase letter.
    ///
    pub def isUpperCase(cp: Int32): Bool =
        Character.isUpperCase(cp)

    ///
    /// Returns `true` if the given code point `cp` represents a titlecase letter.
    ///
    pub def isTitleCase(cp: Int32): Bool =
        Character.isTitleCase(cp)

    ///
    /// Returns `true` if the given code point `cp` represents a white space character.
    ///
    pub def isWhitespace(cp: Int32): Bool =
        Character.isWhitespace(cp)

    ///
    /// Returns `true` if the given code point `cp` represents an alphabetic character.
    ///
    pub def isAlphabetic(cp: Int32): Bool =
        Character.isAlphabetic(cp)

    ///
    /// Returns `true` if the given code point `cp` is within Unicode's Basic Multilingual Plane.
    ///
    /// If the code point is within the BMP it can be represented by Flix's `Char` datatype.
    ///
    pub def isBmpCodePoint(cp: Int32): Bool =
        Character.isBmpCodePoint(cp)

    ///
    /// Returns `true` if the given code point `cp` is defined either as a entry in the
    /// UnicodeData file or a value within a range defined in the UnicodeData file.
    ///
    pub def isDefined(cp: Int32): Bool =
        Character.isDefined(cp)

    ///
    /// Returns `true` if the given code point `cp` is Chinese, Japanase, Korean
    /// or Vietnamese ideograph.
    ///
    pub def isIdeographic(cp: Int32): Bool =
        Character.isIdeographic(cp)

    ///
    /// Returns `true` if the given code point `cp` is an ISO control character.
    ///
    pub def isISOControl(cp: Int32): Bool =
        Character.isISOControl(cp)

    ///
    /// Returns `true` if the given code point `cp` is mirrored.
    ///
    pub def isMirrored(cp: Int32): Bool =
        Character.isMirrored(cp)

    ///
    /// Returns `true` if the given code point `cp` is in Unicode's supplementary character range.
    ///
    pub def isSupplementaryCodePoint(cp: Int32): Bool =
        Character.isSupplementaryCodePoint(cp)

    ///
    /// Returns `true` if the code point `cp` is between `U+0000` and `U+10FFFF`.
    ///
    pub def isValidCodePoint(cp: Int32): Bool =
        Character.isValidCodePoint(cp)

    ///
    /// Converts a code point representing a letter to its lowercase version.
    ///
    /// Returns the original code point if it does not have a lowercase version.
    ///
    pub def toLowerCase(cp: Int32): Int32 =
        Character.toLowerCase(cp)

    ///
    /// Converts a code point representing a letter to its uppercase version.
    ///
    /// Returns the original code point if it does not have a uppercase version.
    ///
    pub def toUpperCase(cp: Int32): Int32 =
        Character.toUpperCase(cp)

    ///
    /// Converts a code point representing a letter to its titlecase version.
    ///
    /// Returns the original character if it does not have either a titlecase
    /// version or a mapping to uppercase.
    ///
    pub def toTitleCase(cp: Int32): Int32 =
        Character.toTitleCase(cp)

    ///
    /// Returns the name of the code point `cp`.
    ///
    /// See the Java JDK documentation of the method `java.lang.Character.getName` for a full
    /// description of how names are derived.
    ///
    pub def getName(cp: Int32): Option[String] =
        try {
            Character.getName(cp) |> Object.toOption
        } catch {
            case _: IllegalArgumentException => None
        }

    ///
    /// Returns the integer value represented by the code point `cp` e.g. code point 0x0031
    /// which is the char '1' returns `Some(1)`.
    ///
    /// Returns `None` if `cp` does not represent a number.
    ///
    /// This function handles supplementary characters.
    ///
    pub def getNumericValue(cp: Int32): Option[Int32] =
        match Character.getNumericValue(cp) {
            case i if i < 0 => None
            case i          => Some(i)
        }

    ///
    /// Returns the number of Chars needed to represent the code point `cp`.
    ///
    /// The answer is either 2 for a supplementary character or 1 for any other character.
    ///
    pub def charCount(cp: Int32): Int32 =
        Character.charCount(cp)

    ///
    /// Optionally returns a vector of Chars representing the code point `cp`.
    ///
    /// A valid `Some(_)` answer is either a length 2 Vector for a supplementary character
    /// or a length 1 Vector for any other valid character.
    ///
    /// Returns 'None' if `cp` is not a valid code point.
    ///
    pub def toChars(cp: Int32): Option[Vector[Char]] = try {
        region rc {
            let arr: Array[Char, rc] = Array.empty(rc, 2);
            let size = unchecked_cast(Character.toChars(cp, arr, 0) as _ \ rc);
            Array.toVector(arr) |> Vector.takeLeft(size) |> Some
        }
    } catch {
        case _: IllegalArgumentException => None
    }

    ///
    /// Optionally returns a Char represention the code point `cp` if `cp` is within
    /// Unicode's Basic Multilingual Plane.
    ///
    pub def toBmpChar(cp: Int32): Option[Char] = try {
        region rc {
            let arr: Array[Char, rc] = Array.empty(rc, 2);
            let i = Character.toChars(cp, arr, 0);
            if (i == 1)
                Array.nth(0, arr)
            else
                None
        }
    } catch {
        case _: IllegalArgumentException => None
    }

    ///
    /// Optionally returns a pair of Chars representing the high and low surrogate characters of the
    /// code point `cp` if `cp` is a supplementary character.
    ///
    pub def toSupplementaryChars(cp: Int32): Option[(Char, Char)] = try {
        region rc {
            let arr: Array[Char, rc] = Array.empty(rc, 2);
            let i = Character.toChars(cp, arr, 0);
            if (i == 2)
                Some((Array.get(0, arr), Array.get(1, arr)))
            else
                None
        }
    } catch {
        case _: IllegalArgumentException => None
    }

    ///
    /// Returns a String representing the code point `cp`.
    ///
    pub def toString(cp: Int32): String =
        try {
            Character.toString(cp)
        } catch {
            case _: IllegalArgumentException => ""
        }

    ///
    /// Optionally returns the high surrogate character of the code point `cp`
    /// if `cp` is a supplementary character.
    ///
    pub def highSurrogate(cp: Int32): Option[Char] =
        if (isSupplementaryCodePoint(cp))
            Some(Character.highSurrogate(cp))
        else
            None

    ///
    /// Optionally returns the low surrogate character of the code point `cp`
    /// if `cp` is a supplementary character.
    ///
    pub def lowSurrogate(cp: Int32): Option[Char] =
        if (isSupplementaryCodePoint(cp))
            Some(Character.lowSurrogate(cp))
        else
            None

}