use super::super::lexeme::LexemeKind;
const HEX: LexemeKind = LexemeKind::CharacterHex;
const PLAIN: LexemeKind = LexemeKind::CharacterPlain;
const UNICODE: LexemeKind = LexemeKind::CharacterUnicode;
const UNDETECTED: (LexemeKind, usize) = (LexemeKind::Undetected, 0);
pub fn detect_character(
orig: &str,
chr: usize,
) -> (
LexemeKind,
usize,
) {
let len = orig.len();
if len < chr + 3 { return UNDETECTED } let c0 = get_aot(orig, chr);
if c0 != "'" { return UNDETECTED }
let mut c1_end = chr + 2;
while !orig.is_char_boundary(c1_end) { c1_end += 1 }
if len < c1_end + 1 { return UNDETECTED }
let c1 = &orig[chr+1..c1_end];
if c1 != "\\" {
return
if c1 == "'"
{ UNDETECTED }
else if get_aot(orig, c1_end) != "'"
{ UNDETECTED }
else { (PLAIN, c1_end + 1) }
}
match get_aot(orig, chr+2) {
"n" | "r" | "t" | "\\" | "0" | "\"" | "'" =>
if len >= chr + 4
&& get_aot(orig, chr+3) == "'"
{ (PLAIN, chr + 4) } else { UNDETECTED },
"x" =>
if len >= chr + 6
&& get_aot(orig, chr+3).chars().all(|c| c >= '0' && c <= '7')
&& get_aot(orig, chr+4).chars().all(|c| c.is_ascii_hexdigit())
&& get_aot(orig, chr+5) == "'"
{ (HEX, chr + 6) } else { UNDETECTED },
"u" =>
detect_unicode_char(orig, chr, len),
_ =>
UNDETECTED
}
}
fn get_aot(orig: &str, c: usize) -> &str { orig.get(c..c+1).unwrap_or("~") }
fn detect_unicode_char(
orig: &str,
chr: usize,
len: usize,
) -> (
LexemeKind,
usize,
) {
if len < chr + 7 || get_aot(orig, chr+3) != "{" { return UNDETECTED }
let mut found_closing_curly_bracket = false;
let mut codepoint = "".to_string();
for i in 4..11 {
let c = get_aot(orig, chr+i);
if c == "}" { found_closing_curly_bracket = true; break }
if c.chars().all(|c| c.is_ascii_hexdigit()) {
codepoint.push_str(c)
} else {
return UNDETECTED
}
}
if ! found_closing_curly_bracket { return UNDETECTED }
let l = codepoint.len() + 5;
if get_aot(orig, chr+l) != "'" { return UNDETECTED }
match u32::from_str_radix(&codepoint, 16) {
Err(_) => UNDETECTED,
Ok(value) =>
if value > 0x10FFFF { UNDETECTED } else { (UNICODE, chr + l + 1) },
}
}
#[cfg(test)]
mod tests {
use super::detect_character as detect;
use super::HEX as H;
use super::PLAIN as P;
use super::UNICODE as C;
use super::UNDETECTED as U;
#[test]
fn get_ascii_or_tilde() {
let orig = "abcd€f";
assert_eq!(orig.get(0..0+1).unwrap_or("~"), "a");
assert_eq!(orig.get(1..1+1).unwrap_or("~"), "b");
assert_eq!(orig.get(4..4+1).unwrap_or("~"), "~"); assert_eq!(orig.get(5..5+1).unwrap_or("~"), "~"); assert_eq!(orig.get(7..7+1).unwrap_or("~"), "f");
assert_eq!(orig.get(8..8+1).unwrap_or("~"), "~"); assert_eq!(orig.get(9..9+1).unwrap_or("~"), "~"); }
#[test]
fn detect_character_correct() {
let orig = "abcde'f'ghi";
assert_eq!(detect(orig, 4), U); assert_eq!(detect(orig, 5), (P,8)); assert_eq!(detect(orig, 6), U); assert_eq!(detect(orig, 7), U); let orig = "±'±'∆'∆'\u{10FFFF}'\u{10FFFF}'";
assert_eq!(detect(orig, 0), U); assert_eq!(detect(orig, 2), (P,6)); assert_eq!(detect(orig, 6), U); assert_eq!(detect(orig, 9), (P,14)); assert_eq!(detect(orig, 14), U); assert_eq!(detect(orig, 18), (P,24)); let orig = " -'\\n'- ";
assert_eq!(detect(orig, 1), U); assert_eq!(detect(orig, 2), (P,6)); assert_eq!(detect(orig, 3), U); assert_eq!(detect("'\\r'", 0), (P,4)); assert_eq!(detect("'\\t' ", 0), (P,4)); assert_eq!(detect("'\\\\'", 0), (P,4)); assert_eq!(detect(" '\\0'", 1), (P,5)); assert_eq!(detect("'\\\"'", 0), (P,4)); assert_eq!(detect("'\\''", 0), (P,4)); let orig = "'\\x4A'";
assert_eq!(detect(orig, 0), (H,6)); assert_eq!(detect(orig, 1), U); assert_eq!(detect(orig, 5), U); let orig = " - '\\x0f' - ";
assert_eq!(detect(orig, 3), (H,9)); assert_eq!(detect("'\\u{0}'", 0), (C,7)); assert_eq!(detect(" '\\u{C}'", 1), (C,8)); assert_eq!(detect("- '\\u{f}'", 2), (C,9)); assert_eq!(detect("'\\u{00}'", 0), (C,8)); assert_eq!(detect(" '\\u{bD}'", 1), (C,9)); assert_eq!(detect("'\\u{1cF}'", 0), (C,9)); assert_eq!(detect("'\\u{fFfF}'", 0), (C,10)); assert_eq!(detect(" '\\u{00000}'", 1), (C,12)); assert_eq!(detect("'\\u{100abC}'", 0), (C,12)); assert_eq!(detect(" - '\\u{10FFFF}'", 3), (C,15)); assert_eq!(detect("'\\u{123}'€", 0), (C,9)); let orig = "'\\u{30aF}'";
assert_eq!(detect(orig, 0), (C,10)); assert_eq!(detect(orig, 1), U); assert_eq!(detect(orig, 2), U); }
#[test]
fn detect_character_incorrect() {
assert_eq!(detect("'' ", 0), U); assert_eq!(detect("'\\' ", 0), U); assert_eq!(detect(" '\\\\", 1), U); assert_eq!(detect("'\\q'", 0), U); assert_eq!(detect("'\\~'", 0), U); assert_eq!(detect(" '\\x'", 1), U); assert_eq!(detect("'\\u'", 0), U); assert_eq!(detect("'\\x3' - ", 0), U); assert_eq!(detect("'\\x3f - ", 0), U); assert_eq!(detect("'\\x0G'", 0), U); assert_eq!(detect("'\\x81'", 0), U); assert_eq!(detect("'\\uxyz", 0), U); assert_eq!(detect("'\\u{xyz", 0), U); assert_eq!(detect("'\\u{0xyz", 0), U); assert_eq!(detect("'\\u", 0), U); assert_eq!(detect("'\\u{", 0), U); assert_eq!(detect("'\\u{0", 0), U); assert_eq!(detect("'\\u[0]'", 0), U); assert_eq!(detect("'\\u{abcde", 0), U); assert_eq!(detect("'\\u{12i4}'", 0), U); assert_eq!(detect("'\\u{100abCd}'", 0), U); assert_eq!(detect("'\\u{1234}", 0), U); assert_eq!(detect("'\\u{1234} ", 0), U); assert_eq!(detect("'\\u{110000}'", 0), U); }
#[test]
fn detect_character_will_not_panic() {
assert_eq!(detect("", 0), U); assert_eq!(detect("'", 0), U); assert_eq!(detect("'a", 0), U); assert_eq!(detect("'\\", 0), U); assert_eq!(detect("'\\n", 0), U); assert_eq!(detect("'\\x", 0), U); assert_eq!(detect("'\\x4", 0), U); assert_eq!(detect("'\\x7f", 0), U); assert_eq!(detect("'\\u", 0), U); assert_eq!(detect("'\\u{", 0), U); assert_eq!(detect("'\\u{0", 0), U); assert_eq!(detect("'\\u{0}", 0), U); assert_eq!(detect("'\\u{30aF", 0), U); assert_eq!(detect("'\\u{30Af}", 0), U); assert_eq!(detect("abc", 2), U); assert_eq!(detect("abc", 3), U); assert_eq!(detect("abc", 4), U); assert_eq!(detect("abc", 100), U); assert_eq!(detect("€", 1), U); assert_eq!(detect("'€", 0), U); assert_eq!(detect("'\\€", 0), U); assert_eq!(detect("'\\u€'", 0), U); assert_eq!(detect("'\\u{€'", 0), U); assert_eq!(detect("'\\u{123€'", 0), U); assert_eq!(detect("'\\u{123}€'", 0), U); }
}