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