1use super::prelude::*;
2use crate::LengthPercentage;
3use css_parse::Token;
4
5#[derive(ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
22#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
23#[derive(csskit_derives::NodeWithMetadata)]
24pub enum Position {
25 SingleValue(PositionSingleValue),
26 TwoValue(PositionHorizontal, PositionVertical),
27 FourValue(PositionHorizontalKeyword, LengthPercentage, PositionVerticalKeyword, LengthPercentage),
28}
29
30impl<'a> Peek<'a> for Position {
31 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
32 where
33 I: Iterator<Item = Cursor> + Clone,
34 {
35 PositionSingleValue::peek(p, c)
36 }
37}
38
39impl<'a> Parse<'a> for Position {
40 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
41 where
42 I: Iterator<Item = Cursor> + Clone,
43 {
44 let first = p.parse::<PositionSingleValue>()?;
45 if !p.peek::<PositionSingleValue>() {
47 return Ok(Self::SingleValue(first));
48 }
49 let second = p.parse::<PositionSingleValue>()?;
50 if !p.peek::<PositionSingleValue>() {
52 if let Some(horizontal) = first.to_horizontal() {
53 if let Some(vertical) = second.to_vertical() {
54 return Ok(Self::TwoValue(horizontal, vertical));
55 }
56 } else if let Some(horizontal) = second.to_horizontal() {
57 if let Some(vertical) = first.to_vertical() {
58 return Ok(Self::TwoValue(horizontal, vertical));
59 } else {
60 Err(Diagnostic::new(second.into(), Diagnostic::unexpected))?
61 }
62 }
63 }
64 if matches!(first, PositionSingleValue::Center(_) | PositionSingleValue::LengthPercentage(_))
66 || !matches!(&second, PositionSingleValue::LengthPercentage(_))
67 {
68 Err(Diagnostic::new(second.into(), Diagnostic::unexpected))?
69 }
70 let third = p.parse::<PositionSingleValue>()?;
71 if third.to_horizontal_keyword().is_none() && third.to_vertical_keyword().is_none() {
72 let cursor: Cursor = third.into();
73 Err(Diagnostic::new(cursor, Diagnostic::expected_ident))?
74 }
75 let fourth = p.parse::<LengthPercentage>()?;
76 if let PositionSingleValue::LengthPercentage(second) = second {
77 if let Some(horizontal) = first.to_horizontal_keyword() {
78 if let Some(vertical) = third.to_vertical_keyword() {
79 Ok(Self::FourValue(horizontal, second, vertical, fourth))
80 } else {
81 Err(Diagnostic::new(third.into(), Diagnostic::unexpected))?
82 }
83 } else if let Some(horizontal) = third.to_horizontal_keyword() {
84 if let Some(vertical) = first.to_vertical_keyword() {
85 Ok(Self::FourValue(horizontal, fourth, vertical, second))
86 } else {
87 Err(Diagnostic::new(third.into(), Diagnostic::unexpected))?
88 }
89 } else {
90 Err(Diagnostic::new(third.into(), Diagnostic::unexpected))?
91 }
92 } else {
93 Err(Diagnostic::new(second.into(), Diagnostic::unexpected))?
94 }
95 }
96}
97
98#[derive(Parse, Peek, IntoCursor, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
100pub enum PositionSingleValue {
101 #[atom(CssAtomSet::Left)]
102 Left(T![Ident]),
103 #[atom(CssAtomSet::Right)]
104 Right(T![Ident]),
105 #[atom(CssAtomSet::Center)]
106 Center(T![Ident]),
107 #[atom(CssAtomSet::Top)]
108 Top(T![Ident]),
109 #[atom(CssAtomSet::Bottom)]
110 Bottom(T![Ident]),
111 LengthPercentage(LengthPercentage),
112}
113
114impl PositionSingleValue {
115 #[inline]
116 fn to_horizontal(self) -> Option<PositionHorizontal> {
117 match self {
118 Self::Left(t) => Some(PositionHorizontal::Left(t)),
119 Self::Right(t) => Some(PositionHorizontal::Right(t)),
120 Self::Center(t) => Some(PositionHorizontal::Center(t)),
121 Self::LengthPercentage(l) => Some(PositionHorizontal::LengthPercentage(l)),
122 _ => None,
123 }
124 }
125
126 #[inline]
127 fn to_vertical(self) -> Option<PositionVertical> {
128 match self {
129 Self::Top(t) => Some(PositionVertical::Top(t)),
130 Self::Bottom(t) => Some(PositionVertical::Bottom(t)),
131 Self::Center(t) => Some(PositionVertical::Center(t)),
132 Self::LengthPercentage(l) => Some(PositionVertical::LengthPercentage(l)),
133 _ => None,
134 }
135 }
136
137 #[inline]
138 fn to_horizontal_keyword(self) -> Option<PositionHorizontalKeyword> {
139 match self {
140 Self::Left(t) => Some(PositionHorizontalKeyword::Left(t)),
141 Self::Right(t) => Some(PositionHorizontalKeyword::Right(t)),
142 _ => None,
143 }
144 }
145
146 #[inline]
147 fn to_vertical_keyword(self) -> Option<PositionVerticalKeyword> {
148 match self {
149 Self::Top(t) => Some(PositionVerticalKeyword::Top(t)),
150 Self::Bottom(t) => Some(PositionVerticalKeyword::Bottom(t)),
151 _ => None,
152 }
153 }
154}
155
156impl From<PositionSingleValue> for Kind {
157 fn from(value: PositionSingleValue) -> Self {
158 let t: Token = value.into();
159 t.into()
160 }
161}
162
163#[derive(IntoCursor, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
164#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
165pub enum PositionHorizontal {
166 Left(T![Ident]),
167 Right(T![Ident]),
168 Center(T![Ident]),
169 LengthPercentage(LengthPercentage),
170}
171
172#[derive(IntoCursor, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
174pub enum PositionVertical {
175 Top(T![Ident]),
176 Bottom(T![Ident]),
177 Center(T![Ident]),
178 LengthPercentage(LengthPercentage),
179}
180
181#[derive(Parse, Peek, IntoCursor, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
182#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
183#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
184#[derive(csskit_derives::NodeWithMetadata)]
185pub enum PositionHorizontalKeyword {
186 #[atom(CssAtomSet::Left)]
187 Left(T![Ident]),
188 #[atom(CssAtomSet::Right)]
189 Right(T![Ident]),
190}
191
192#[derive(Parse, Peek, IntoCursor, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
193#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
194#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
195#[derive(csskit_derives::NodeWithMetadata)]
196pub enum PositionVerticalKeyword {
197 #[atom(CssAtomSet::Top)]
198 Top(T![Ident]),
199 #[atom(CssAtomSet::Bottom)]
200 Bottom(T![Ident]),
201}
202
203#[cfg(test)]
204mod tests {
205 use crate::Length;
206
207 use super::*;
208 use crate::CssAtomSet;
209 use css_parse::{assert_parse, assert_parse_error, assert_parse_span};
210
211 #[test]
212 fn size_test() {
213 assert_eq!(std::mem::size_of::<Position>(), 64);
214 }
215
216 #[test]
217 fn test_writes() {
218 assert_parse!(CssAtomSet::ATOMS, Position, "left", Position::SingleValue(PositionSingleValue::Left(_)));
219 assert_parse!(CssAtomSet::ATOMS, Position, "right", Position::SingleValue(PositionSingleValue::Right(_)));
220 assert_parse!(CssAtomSet::ATOMS, Position, "top", Position::SingleValue(PositionSingleValue::Top(_)));
221 assert_parse!(CssAtomSet::ATOMS, Position, "bottom", Position::SingleValue(PositionSingleValue::Bottom(_)));
222 assert_parse!(CssAtomSet::ATOMS, Position, "center", Position::SingleValue(PositionSingleValue::Center(_)));
223 assert_parse!(
224 CssAtomSet::ATOMS,
225 Position,
226 "center center",
227 Position::TwoValue(PositionHorizontal::Center(_), PositionVertical::Center(_))
228 );
229 assert_parse!(
230 CssAtomSet::ATOMS,
231 Position,
232 "center top",
233 Position::TwoValue(PositionHorizontal::Center(_), PositionVertical::Top(_))
234 );
235 assert_parse!(
236 CssAtomSet::ATOMS,
237 Position,
238 "50% 50%",
239 Position::TwoValue(
240 PositionHorizontal::LengthPercentage(LengthPercentage::Percent(_)),
241 PositionVertical::LengthPercentage(LengthPercentage::Percent(_))
242 )
243 );
244 assert_parse!(
245 CssAtomSet::ATOMS,
246 Position,
247 "50%",
248 Position::SingleValue(PositionSingleValue::LengthPercentage(LengthPercentage::Percent(_)))
249 );
250 assert_parse!(
251 CssAtomSet::ATOMS,
252 Position,
253 "20px 30px",
254 Position::TwoValue(
255 PositionHorizontal::LengthPercentage(LengthPercentage::Length(Length::Px(_))),
256 PositionVertical::LengthPercentage(LengthPercentage::Length(Length::Px(_)))
257 )
258 );
259 assert_parse!(
260 CssAtomSet::ATOMS,
261 Position,
262 "2% bottom",
263 Position::TwoValue(
264 PositionHorizontal::LengthPercentage(LengthPercentage::Percent(_)),
265 PositionVertical::Bottom(_)
266 )
267 );
268 assert_parse!(
269 CssAtomSet::ATOMS,
270 Position,
271 "-70% -180%",
272 Position::TwoValue(
273 PositionHorizontal::LengthPercentage(LengthPercentage::Percent(_)),
274 PositionVertical::LengthPercentage(LengthPercentage::Percent(_))
275 )
276 );
277 assert_parse!(
278 CssAtomSet::ATOMS,
279 Position,
280 "right 8.5%",
281 Position::TwoValue(
282 PositionHorizontal::Right(_),
283 PositionVertical::LengthPercentage(LengthPercentage::Percent(_))
284 )
285 );
286 assert_parse!(
287 CssAtomSet::ATOMS,
288 Position,
289 "right -6px bottom 12vmin",
290 Position::FourValue(
291 PositionHorizontalKeyword::Right(_),
292 LengthPercentage::Length(Length::Px(_)),
293 PositionVerticalKeyword::Bottom(_),
294 LengthPercentage::Length(Length::Vmin(_))
295 )
296 );
297 assert_parse!(
298 CssAtomSet::ATOMS,
299 Position,
300 "bottom 12vmin right -6px",
301 Position::FourValue(
302 PositionHorizontalKeyword::Right(_),
303 LengthPercentage::Length(Length::Px(_)),
304 PositionVerticalKeyword::Bottom(_),
305 LengthPercentage::Length(Length::Vmin(_))
306 )
307 );
308 }
309
310 #[test]
311 fn test_errors() {
312 assert_parse_error!(CssAtomSet::ATOMS, Position, "left left");
313 assert_parse_error!(CssAtomSet::ATOMS, Position, "bottom top");
314 assert_parse_error!(CssAtomSet::ATOMS, Position, "10px 15px 20px 15px");
315 assert_parse_error!(CssAtomSet::ATOMS, Position, "right -6px bottom");
317 }
318
319 #[test]
320 fn test_spans() {
321 assert_parse_span!(
323 CssAtomSet::ATOMS,
324 Position,
325 r#"
326 right var(--foo)
327 ^^^^^
328 "#
329 );
330 assert_parse_span!(
332 CssAtomSet::ATOMS,
333 Position,
334 r#"
335 right -6px bottom 12rem 8px 20%
336 ^^^^^^^^^^^^^^^^^^^^^^^
337 "#
338 );
339 }
340
341 }