css_ast/functions/
attr_function.rs

1use crate::{Syntax, diagnostics};
2use css_parse::{
3	ComponentValues, Cursor, Function, Parse, Parser, Peek, Result as ParserResult, T, function_set, keyword_set,
4};
5use csskit_derives::{Parse, Peek, ToCursors, ToSpan, Visitable};
6
7function_set!(pub struct AttrFunctionName "attr");
8
9/// <https://drafts.csswg.org/css-values-5/#attr-notation>
10///
11/// ```text,ignore
12/// attr() = attr( <attr-name> <attr-type>? , <declaration-value>?)
13/// <attr-type> = type( <syntax> ) | raw-string | <attr-unit>
14/// ```
15#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
17#[visit(self)]
18pub struct AttrFunction<'a>(Function<AttrFunctionName, AttrFunctionParams<'a>>);
19
20#[derive(Parse, Peek, ToSpan, ToCursors, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
22pub struct AttrFunctionParams<'a>(AttrName, Option<AttrType>, Option<T![,]>, Option<ComponentValues<'a>>);
23
24// <attr-name> = [ <ident-token>? '|' ]? <ident-token>
25#[derive(ToSpan, ToCursors, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
27pub struct AttrName(pub Option<T![Ident]>, pub Option<T![|]>, pub Option<T![Ident]>);
28
29impl<'a> Peek<'a> for AttrName {
30	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
31		<T![Ident]>::peek(p, c)
32	}
33}
34
35impl<'a> Parse<'a> for AttrName {
36	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
37		let a = p.parse_if_peek::<T![Ident]>()?;
38		let b = p.parse_if_peek::<T![|]>()?;
39
40		if a.is_some() && b.is_none() {
41			return Ok(Self(None, None, a));
42		}
43
44		if a.is_none() && b.is_some() {
45			return Ok(Self(None, b, Some(p.parse::<T![Ident]>()?)));
46		}
47
48		if a.is_none() && b.is_none() {
49			Err(diagnostics::ExpectedIdent(p.next()))?
50		}
51
52		debug_assert!(a.is_some() && b.is_some());
53
54		Ok(Self(a, b, Some(p.parse::<T![Ident]>()?)))
55	}
56}
57
58keyword_set!(pub struct AttrTypeKeywords "raw-string");
59
60function_set!(pub struct AttrTypeFunctionName "type");
61
62// <attr-type> = type( <syntax> ) | raw-string | <attr-unit>
63#[derive(ToSpan, ToCursors, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
65pub enum AttrType {
66	Type(Function<AttrTypeFunctionName, Syntax>),
67	RawString(T![Ident]),
68	Unit(T![DimensionIdent]),
69}
70
71impl<'a> Peek<'a> for AttrType {
72	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
73		AttrTypeKeywords::peek(p, c) || <T![DimensionIdent]>::peek(p, c) || AttrTypeFunctionName::peek(p, c)
74	}
75}
76
77impl<'a> Parse<'a> for AttrType {
78	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
79		if let Some(raw) = p.parse_if_peek::<AttrTypeKeywords>()? {
80			return Ok(Self::RawString(raw.into()));
81		}
82		if let Some(unit) = p.parse_if_peek::<T![DimensionIdent]>()? {
83			return Ok(Self::Unit(unit));
84		}
85		p.parse::<Function<AttrTypeFunctionName, Syntax>>().map(Self::Type)
86	}
87}
88
89#[cfg(test)]
90mod tests {
91	use super::*;
92	use css_parse::{assert_parse, assert_parse_error};
93
94	#[test]
95	fn size_test() {
96		assert_eq!(std::mem::size_of::<AttrFunction>(), 160);
97	}
98
99	#[test]
100	fn test_writes() {
101		assert_parse!(AttrFunction, "attr(foo)");
102		assert_parse!(AttrFunction, "attr(foo)");
103		assert_parse!(AttrFunction, "attr(bar px)");
104		assert_parse!(AttrFunction, "attr(foo|bar px)");
105		assert_parse!(AttrFunction, "attr(foo|bar)");
106		assert_parse!(AttrFunction, "attr(|bar)");
107		assert_parse!(AttrFunction, "attr(|bar px)");
108	}
109
110	#[test]
111	fn test_errors() {
112		assert_parse_error!(AttrName, "a|b|c");
113		assert_parse_error!(AttrFunction, "attrr(foo)");
114		assert_parse_error!(AttrFunction, "attr()");
115		assert_parse_error!(AttrFunction, "attr(|)");
116	}
117}