use {Parser, ToCss};
use std::char;
use std::cmp;
use std::fmt;
use std::io::{self, Write};
use tokenizer::{Token, NumericValue};
#[derive(PartialEq, Eq, Clone, Hash)]
pub struct UnicodeRange {
pub start: u32,
pub end: u32,
}
impl UnicodeRange {
pub fn parse(input: &mut Parser) -> Result<Self, ()> {
input.expect_ident_matching("u")?;
const MAX_LENGTH_AFTER_U_PLUS: usize = 6 + 1 + 6; let mut buffer = [0; MAX_LENGTH_AFTER_U_PLUS];
let remaining_len;
{
let mut remaining = &mut buffer[..];
concatenate_tokens(input, &mut remaining)?;
remaining_len = remaining.len();
}
let text_len = buffer.len() - remaining_len;
let text = &buffer[..text_len];
let range = parse_concatenated(text)?;
if range.end > char::MAX as u32 || range.start > range.end {
Err(())
} else {
Ok(range)
}
}
}
fn concatenate_tokens(input: &mut Parser, remaining: &mut &mut [u8]) -> Result<(), Error> {
match input.next_including_whitespace()? {
Token::Delim('+') => {
match input.next_including_whitespace()? {
Token::Ident(ident) => remaining.write_all(ident.as_bytes())?,
Token::Delim('?') => remaining.write_all(b"?")?,
_ => return Err(Error)
}
parse_question_marks(input, remaining)
}
Token::Dimension(ref value, ref unit) => {
let int_value = positive_integer_with_plus_sign(value)?;
write!(remaining, "{}{}", int_value, unit)?;
parse_question_marks(input, remaining)
}
Token::Number(ref value) => {
let int_value = positive_integer_with_plus_sign(value)?;
write!(remaining, "{}", int_value)?;
match input.next_including_whitespace() {
Err(()) => {},
Ok(Token::Delim('?')) => {
remaining.write_all(b"?")?;
parse_question_marks(input, remaining)
}
Ok(Token::Dimension(ref value, ref unit)) => {
let int_value = negative_integer(value)?;
write!(remaining, "{}{}", int_value, unit)?
}
Ok(Token::Number(ref value)) => {
let int_value = negative_integer(value)?;
write!(remaining, "{}", int_value)?
}
_ => return Err(Error)
}
}
_ => return Err(Error)
}
Ok(())
}
fn parse_question_marks(input: &mut Parser, remaining: &mut &mut [u8]) {
loop {
let result = input.try(|input| {
match input.next_including_whitespace() {
Ok(Token::Delim('?')) => remaining.write_all(b"?").map_err(|_| ()),
_ => Err(())
}
});
if result.is_err() {
return
}
}
}
fn positive_integer_with_plus_sign(value: &NumericValue) -> Result<i32, ()> {
let int_value = value.int_value.ok_or(())?;
if value.has_sign && int_value >= 0 {
Ok(int_value)
} else {
Err(())
}
}
fn negative_integer(value: &NumericValue) -> Result<i32, ()> { let int_value = value.int_value.ok_or(())?;
if int_value <= 0 {
Ok(int_value)
} else {
Err(())
}
}
fn parse_concatenated(mut text: &[u8]) -> Result<UnicodeRange, ()> {
let (first_hex_value, hex_digit_count) = consume_hex(&mut text);
let question_marks = consume_question_marks(&mut text);
let consumed = hex_digit_count + question_marks;
if consumed == 0 || consumed > 6 {
return Err(())
}
if question_marks > 0 {
if text.is_empty() {
return Ok(UnicodeRange {
start: first_hex_value << (question_marks * 4),
end: ((first_hex_value + 1) << (question_marks * 4)) - 1,
})
}
} else if text.is_empty() {
return Ok(UnicodeRange {
start: first_hex_value,
end: first_hex_value,
})
} else {
if let Some((&b'-', mut text)) = text.split_first() {
let (second_hex_value, hex_digit_count) = consume_hex(&mut text);
if hex_digit_count > 0 && hex_digit_count <= 6 && text.is_empty() {
return Ok(UnicodeRange {
start: first_hex_value,
end: second_hex_value,
})
}
}
}
Err(())
}
fn consume_hex(text: &mut &[u8]) -> (u32, usize) {
let mut value = 0;
let mut digits = 0;
while let Some((&byte, rest)) = text.split_first() {
if let Some(digit_value) = (byte as char).to_digit(16) {
value = value * 0x10 + digit_value;
digits += 1;
*text = rest
} else {
break
}
}
(value, digits)
}
fn consume_question_marks(text: &mut &[u8]) -> usize {
let mut question_marks = 0;
while let Some((&b'?', rest)) = text.split_first() {
question_marks += 1;
*text = rest
}
question_marks
}
impl fmt::Debug for UnicodeRange {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
self.to_css(formatter)
}
}
impl ToCss for UnicodeRange {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
dest.write_str("U+")?;
let bits = cmp::min(self.start.trailing_zeros(), (!self.end).trailing_zeros());
let question_marks = bits / 4;
let bits = question_marks * 4;
let truncated_start = self.start >> bits;
let truncated_end = self.end >> bits;
if truncated_start == truncated_end {
if truncated_start != 0 {
write!(dest, "{:X}", truncated_start)?;
}
for _ in 0..question_marks {
dest.write_str("?")?;
}
} else {
write!(dest, "{:X}", self.start)?;
if self.end != self.start {
write!(dest, "-{:X}", self.end)?;
}
}
Ok(())
}
}
struct Error;
impl From<Error> for () {
fn from(_: Error) -> Self { () }
}
impl From<()> for Error {
fn from(_: ()) -> Self { Error }
}
impl From<io::Error> for Error {
fn from(_: io::Error) -> Self { Error }
}