css_ast/types/
unicode_range.rs1use super::prelude::*;
2use css_lexer::{Feature as LexerFeature, Lexer, SourceOffset};
3
4#[node]
15#[derive(IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
17#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
18#[derive(csskit_derives::NodeWithMetadata)]
19pub struct UnicodeRange(#[metadata(skip)] Cursor);
20
21impl UnicodeRange {
22 pub fn start(&self) -> u32 {
24 self.0.token().unicode_range_start()
25 }
26
27 pub fn end(&self) -> u32 {
29 self.0.token().unicode_range_end()
30 }
31}
32
33impl<'a> Peek<'a> for UnicodeRange {
34 const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::Ident]);
35
36 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
37 where
38 I: Iterator<Item = Cursor> + Clone,
39 {
40 if c != Kind::Ident {
41 return false;
42 }
43 let source = p.source_text();
44 if !c.str_slice(source).eq_ignore_ascii_case("u") {
45 return false;
46 }
47 source[c.end_offset().0 as usize..].starts_with('+')
48 }
49}
50
51impl<'a> Parse<'a> for UnicodeRange {
52 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
53 where
54 I: Iterator<Item = Cursor> + Clone,
55 {
56 let c = p.peek_n(1);
57 if !p.peek::<Self>() {
58 Err(Diagnostic::new(c, Diagnostic::invalid_unicode_range))?
59 }
60 let offset = c.offset().0 as usize;
61 let token =
62 Lexer::new_with_features(&CssAtomSet::ATOMS, &p.source_text()[offset..], LexerFeature::UnicodeRange)
63 .advance();
64 if token != Kind::UnicodeRange || token.is_bad() {
65 Err(Diagnostic::new(c, Diagnostic::invalid_unicode_range))?
66 }
67 let end = offset + token.len() as usize;
68 let skip = p.set_skip(KindSet::NONE);
69 let result = loop {
70 let consumed = p.next();
71 match consumed.end_offset().0 as usize {
72 at if at == end => break Ok(()),
73 at if at > end || consumed == Kind::Eof => {
74 break Err(Diagnostic::new(c, Diagnostic::invalid_unicode_range));
75 }
76 _ => {}
77 }
78 };
79 p.set_skip(skip);
80 result?;
81 Ok(Self(Cursor::new(SourceOffset(offset as u32), token)))
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use css_parse::{assert_parse, assert_parse_error, assert_peek_false};
89
90 #[test]
91 fn test_writes() {
92 assert_parse!(CssAtomSet::ATOMS, UnicodeRange, "U+26");
93 assert_parse!(CssAtomSet::ATOMS, UnicodeRange, "U+0-7F");
94 assert_parse!(CssAtomSet::ATOMS, UnicodeRange, "u+590-5ff");
95 assert_parse!(CssAtomSet::ATOMS, UnicodeRange, "U+30??");
96 assert_parse!(CssAtomSet::ATOMS, UnicodeRange, "U+4E00-9FFF");
97 assert_parse!(CssAtomSet::ATOMS, UnicodeRange, "U+0-10FFFF");
98 assert_parse!(CssAtomSet::ATOMS, UnicodeRange, "U+??????");
99 }
100
101 #[test]
102 fn test_values() {
103 let alloc = css_parse::Arena::new();
104 let source = "U+4E00-9FFF";
105 let lexer = Lexer::new(&CssAtomSet::ATOMS, source);
106 let mut p = Parser::new(&alloc, source, lexer);
107 let range = p.parse_entirely::<UnicodeRange>().output.expect("parses");
108 assert_eq!(range.start(), 0x4E00);
109 assert_eq!(range.end(), 0x9FFF);
110 }
111
112 #[test]
113 fn test_errors() {
114 assert_peek_false!(CssAtomSet::ATOMS, UnicodeRange, "U");
115 assert_peek_false!(CssAtomSet::ATOMS, UnicodeRange, "url(a)");
116 assert_peek_false!(CssAtomSet::ATOMS, UnicodeRange, "1px");
117 assert_parse_error!(CssAtomSet::ATOMS, UnicodeRange, "U+ZZ");
118 }
119}