// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Utilities for manipulating the char type use option::{None, Option, Some}; use int; use str::StrSlice; use unicode::{derived_property, general_category}; #[cfg(test)] use str::OwnedStr; #[cfg(not(test))] use cmp::{Eq, Ord}; #[cfg(not(test))] use num::Zero; /* Lu Uppercase_Letter an uppercase letter Ll Lowercase_Letter a lowercase letter Lt Titlecase_Letter a digraphic character, with first part uppercase Lm Modifier_Letter a modifier letter Lo Other_Letter other letters, including syllables and ideographs Mn Nonspacing_Mark a nonspacing combining mark (zero advance width) Mc Spacing_Mark a spacing combining mark (positive advance width) Me Enclosing_Mark an enclosing combining mark Nd Decimal_Number a decimal digit Nl Letter_Number a letterlike numeric character No Other_Number a numeric character of other type Pc Connector_Punctuation a connecting punctuation mark, like a tie Pd Dash_Punctuation a dash or hyphen punctuation mark Ps Open_Punctuation an opening punctuation mark (of a pair) Pe Close_Punctuation a closing punctuation mark (of a pair) Pi Initial_Punctuation an initial quotation mark Pf Final_Punctuation a final quotation mark Po Other_Punctuation a punctuation mark of other type Sm Math_Symbol a symbol of primarily mathematical use Sc Currency_Symbol a currency sign Sk Modifier_Symbol a non-letterlike modifier symbol So Other_Symbol a symbol of other type Zs Space_Separator a space character (of various non-zero widths) Zl Line_Separator U+2028 LINE SEPARATOR only Zp Paragraph_Separator U+2029 PARAGRAPH SEPARATOR only Cc Control a C0 or C1 control code Cf Format a format control character Cs Surrogate a surrogate code point Co Private_Use a private-use character Cn Unassigned a reserved unassigned code point or a noncharacter */ /// Returns whether the specified character is considered a unicode alphabetic /// character pub fn is_alphabetic(c: char) -> bool { derived_property::Alphabetic(c) } #[allow(missing_doc)] pub fn is_XID_start(c: char) -> bool { derived_property::XID_Start(c) } #[allow(missing_doc)] pub fn is_XID_continue(c: char) -> bool { derived_property::XID_Continue(c) } /// /// Indicates whether a character is in lower case, defined /// in terms of the Unicode General Category 'Ll' /// #[inline] pub fn is_lowercase(c: char) -> bool { general_category::Ll(c) } /// /// Indicates whether a character is in upper case, defined /// in terms of the Unicode General Category 'Lu'. /// #[inline] pub fn is_uppercase(c: char) -> bool { general_category::Lu(c) } /// /// Indicates whether a character is whitespace. Whitespace is defined in /// terms of the Unicode General Categories 'Zs', 'Zl', 'Zp' /// additional 'Cc'-category control codes in the range [0x09, 0x0d] /// #[inline] pub fn is_whitespace(c: char) -> bool { c == ' ' || ('\x09' <= c && c <= '\x0d') || general_category::Zs(c) || general_category::Zl(c) || general_category::Zp(c) } /// /// Indicates whether a character is alphanumeric. Alphanumericness is /// defined in terms of the Unicode General Categories 'Nd', 'Nl', 'No' /// and the Derived Core Property 'Alphabetic'. /// #[inline] pub fn is_alphanumeric(c: char) -> bool { derived_property::Alphabetic(c) || general_category::Nd(c) || general_category::Nl(c) || general_category::No(c) } /// Indicates whether the character is numeric (Nd, Nl, or No) #[inline] pub fn is_digit(c: char) -> bool { general_category::Nd(c) || general_category::Nl(c) || general_category::No(c) } /// /// Checks if a character parses as a numeric digit in the given radix. /// Compared to `is_digit()`, this function only recognizes the /// characters `0-9`, `a-z` and `A-Z`. /// /// # Return value /// /// Returns `true` if `c` is a valid digit under `radix`, and `false` /// otherwise. /// /// # Failure /// /// Fails if given a `radix` > 36. /// /// # Note /// /// This just wraps `to_digit()`. /// #[inline] pub fn is_digit_radix(c: char, radix: uint) -> bool { match to_digit(c, radix) { Some(_) => true, None => false, } } /// /// Convert a char to the corresponding digit. /// /// # Return value /// /// If `c` is between '0' and '9', the corresponding value /// between 0 and 9. If `c` is 'a' or 'A', 10. If `c` is /// 'b' or 'B', 11, etc. Returns none if the char does not /// refer to a digit in the given radix. /// /// # Failure /// /// Fails if given a `radix` outside the range `[0..36]`. /// #[inline] pub fn to_digit(c: char, radix: uint) -> Option { if radix > 36 { fail!("to_digit: radix %? is to high (maximum 36)", radix); } let val = match c { '0' .. '9' => c as uint - ('0' as uint), 'a' .. 'z' => c as uint + 10u - ('a' as uint), 'A' .. 'Z' => c as uint + 10u - ('A' as uint), _ => return None, }; if val < radix { Some(val) } else { None } } /// /// Converts a number to the character representing it. /// /// # Return value /// /// Returns `Some(char)` if `num` represents one digit under `radix`, /// using one character of `0-9` or `a-z`, or `None` if it doesn't. /// /// # Failure /// /// Fails if given an `radix` > 36. /// #[inline] pub fn from_digit(num: uint, radix: uint) -> Option { if radix > 36 { fail!("from_digit: radix %? is to high (maximum 36)", num); } if num < radix { if num < 10 { Some(('0' as uint + num) as char) } else { Some(('a' as uint + num - 10u) as char) } } else { None } } /// /// Return the hexadecimal unicode escape of a char. /// /// The rules are as follows: /// /// - chars in [0,0xff] get 2-digit escapes: `\\xNN` /// - chars in [0x100,0xffff] get 4-digit escapes: `\\uNNNN` /// - chars above 0x10000 get 8-digit escapes: `\\UNNNNNNNN` /// pub fn escape_unicode(c: char, f: &fn(char)) { // avoid calling str::to_str_radix because we don't really need to allocate // here. f('\\'); let pad = cond!( (c <= '\xff') { f('x'); 2 } (c <= '\uffff') { f('u'); 4 } _ { f('U'); 8 } ); for int::range_step(4 * (pad - 1), -1, -4) |offset| { match ((c as u32) >> offset) & 0xf { i @ 0 .. 9 => { f('0' + i as char); } i => { f('a' + (i - 10) as char); } } } } /// /// Return a 'default' ASCII and C++11-like char-literal escape of a char. /// /// The default is chosen with a bias toward producing literals that are /// legal in a variety of languages, including C++11 and similar C-family /// languages. The exact rules are: /// /// - Tab, CR and LF are escaped as '\t', '\r' and '\n' respectively. /// - Single-quote, double-quote and backslash chars are backslash-escaped. /// - Any other chars in the range [0x20,0x7e] are not escaped. /// - Any other chars are given hex unicode escapes; see `escape_unicode`. /// pub fn escape_default(c: char, f: &fn(char)) { match c { '\t' => { f('\\'); f('t'); } '\r' => { f('\\'); f('r'); } '\n' => { f('\\'); f('n'); } '\\' => { f('\\'); f('\\'); } '\'' => { f('\\'); f('\''); } '"' => { f('\\'); f('"'); } '\x20' .. '\x7e' => { f(c); } _ => c.escape_unicode(f), } } /// Returns the amount of bytes this character would need if encoded in utf8 pub fn len_utf8_bytes(c: char) -> uint { static MAX_ONE_B: uint = 128u; static MAX_TWO_B: uint = 2048u; static MAX_THREE_B: uint = 65536u; static MAX_FOUR_B: uint = 2097152u; let code = c as uint; cond!( (code < MAX_ONE_B) { 1u } (code < MAX_TWO_B) { 2u } (code < MAX_THREE_B) { 3u } (code < MAX_FOUR_B) { 4u } _ { fail!("invalid character!") } ) } #[allow(missing_doc)] pub trait Char { fn is_alphabetic(&self) -> bool; fn is_XID_start(&self) -> bool; fn is_XID_continue(&self) -> bool; fn is_lowercase(&self) -> bool; fn is_uppercase(&self) -> bool; fn is_whitespace(&self) -> bool; fn is_alphanumeric(&self) -> bool; fn is_digit(&self) -> bool; fn is_digit_radix(&self, radix: uint) -> bool; fn to_digit(&self, radix: uint) -> Option; fn from_digit(num: uint, radix: uint) -> Option; fn escape_unicode(&self, f: &fn(char)); fn escape_default(&self, f: &fn(char)); fn len_utf8_bytes(&self) -> uint; } impl Char for char { fn is_alphabetic(&self) -> bool { is_alphabetic(*self) } fn is_XID_start(&self) -> bool { is_XID_start(*self) } fn is_XID_continue(&self) -> bool { is_XID_continue(*self) } fn is_lowercase(&self) -> bool { is_lowercase(*self) } fn is_uppercase(&self) -> bool { is_uppercase(*self) } fn is_whitespace(&self) -> bool { is_whitespace(*self) } fn is_alphanumeric(&self) -> bool { is_alphanumeric(*self) } fn is_digit(&self) -> bool { is_digit(*self) } fn is_digit_radix(&self, radix: uint) -> bool { is_digit_radix(*self, radix) } fn to_digit(&self, radix: uint) -> Option { to_digit(*self, radix) } fn from_digit(num: uint, radix: uint) -> Option { from_digit(num, radix) } fn escape_unicode(&self, f: &fn(char)) { escape_unicode(*self, f) } fn escape_default(&self, f: &fn(char)) { escape_default(*self, f) } fn len_utf8_bytes(&self) -> uint { len_utf8_bytes(*self) } } #[cfg(not(test))] impl Eq for char { #[inline] fn eq(&self, other: &char) -> bool { (*self) == (*other) } #[inline] fn ne(&self, other: &char) -> bool { (*self) != (*other) } } #[cfg(not(test))] impl Ord for char { #[inline] fn lt(&self, other: &char) -> bool { *self < *other } #[inline] fn le(&self, other: &char) -> bool { *self <= *other } #[inline] fn gt(&self, other: &char) -> bool { *self > *other } #[inline] fn ge(&self, other: &char) -> bool { *self >= *other } } #[cfg(not(test))] impl Zero for char { fn zero() -> char { 0 as char } fn is_zero(&self) -> bool { *self == 0 as char } } #[test] fn test_is_lowercase() { assert!('a'.is_lowercase()); assert!('ö'.is_lowercase()); assert!('ß'.is_lowercase()); assert!(!'Ü'.is_lowercase()); assert!(!'P'.is_lowercase()); } #[test] fn test_is_uppercase() { assert!(!'h'.is_uppercase()); assert!(!'ä'.is_uppercase()); assert!(!'ß'.is_uppercase()); assert!('Ö'.is_uppercase()); assert!('T'.is_uppercase()); } #[test] fn test_is_whitespace() { assert!(' '.is_whitespace()); assert!('\u2007'.is_whitespace()); assert!('\t'.is_whitespace()); assert!('\n'.is_whitespace()); assert!(!'a'.is_whitespace()); assert!(!'_'.is_whitespace()); assert!(!'\u0000'.is_whitespace()); } #[test] fn test_to_digit() { assert_eq!('0'.to_digit(10u), Some(0u)); assert_eq!('1'.to_digit(2u), Some(1u)); assert_eq!('2'.to_digit(3u), Some(2u)); assert_eq!('9'.to_digit(10u), Some(9u)); assert_eq!('a'.to_digit(16u), Some(10u)); assert_eq!('A'.to_digit(16u), Some(10u)); assert_eq!('b'.to_digit(16u), Some(11u)); assert_eq!('B'.to_digit(16u), Some(11u)); assert_eq!('z'.to_digit(36u), Some(35u)); assert_eq!('Z'.to_digit(36u), Some(35u)); assert_eq!(' '.to_digit(10u), None); assert_eq!('$'.to_digit(36u), None); } #[test] fn test_is_digit() { assert!('2'.is_digit()); assert!('7'.is_digit()); assert!(!'c'.is_digit()); assert!(!'i'.is_digit()); assert!(!'z'.is_digit()); assert!(!'Q'.is_digit()); } #[test] fn test_escape_default() { fn string(c: char) -> ~str { let mut result = ~""; do escape_default(c) |c| { result.push_char(c); } return result; } assert_eq!(string('\n'), ~"\\n"); assert_eq!(string('\r'), ~"\\r"); assert_eq!(string('\''), ~"\\'"); assert_eq!(string('"'), ~"\\\""); assert_eq!(string(' '), ~" "); assert_eq!(string('a'), ~"a"); assert_eq!(string('~'), ~"~"); assert_eq!(string('\x00'), ~"\\x00"); assert_eq!(string('\x1f'), ~"\\x1f"); assert_eq!(string('\x7f'), ~"\\x7f"); assert_eq!(string('\xff'), ~"\\xff"); assert_eq!(string('\u011b'), ~"\\u011b"); assert_eq!(string('\U0001d4b6'), ~"\\U0001d4b6"); } #[test] fn test_escape_unicode() { fn string(c: char) -> ~str { let mut result = ~""; do escape_unicode(c) |c| { result.push_char(c); } return result; } assert_eq!(string('\x00'), ~"\\x00"); assert_eq!(string('\n'), ~"\\x0a"); assert_eq!(string(' '), ~"\\x20"); assert_eq!(string('a'), ~"\\x61"); assert_eq!(string('\u011b'), ~"\\u011b"); assert_eq!(string('\U0001d4b6'), ~"\\U0001d4b6"); }