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