loess_rust/lexical_structure/
tokens.rs

1use loess::{Error, ErrorPriority, Errors, Input, IntoTokens, PeekFrom, PopFrom, SimpleSpanned};
2use proc_macro2::{Ident, Punct, Spacing, Span, TokenStream, TokenTree};
3
4/// `->`
5#[derive(Clone)]
6pub struct RArrow(pub Punct, pub Punct);
7
8impl Default for RArrow {
9	fn default() -> Self {
10		Self(
11			Punct::new('-', Spacing::Joint).with_span(Span::mixed_site()),
12			Punct::new('>', Spacing::Alone).with_span(Span::mixed_site()),
13		)
14	}
15}
16
17/// `->`
18impl PeekFrom for RArrow {
19	fn peek_from(input: &Input) -> bool {
20		input.peek(|tts, _| {
21			matches!(tts, [TokenTree::Punct(minus), TokenTree::Punct(gt)]
22			if minus.as_char() == '-' && minus.spacing() == Spacing::Joint && gt.as_char() == '>')
23		})
24	}
25}
26
27impl PopFrom for RArrow {
28	fn pop_from(input: &mut Input, errors: &mut Errors) -> Result<Self, ()> {
29		input
30			.pop_or_replace(|tts, _| match tts {
31				[TokenTree::Punct(minus), TokenTree::Punct(gt)]
32					if minus.as_char() == '-'
33						&& minus.spacing() == Spacing::Joint
34						&& gt.as_char() == '>' =>
35				{
36					Ok(Self(minus, gt))
37				}
38				other => Err(other),
39			})
40			.map_err(|spans| {
41				errors.push(Error::new(ErrorPriority::GRAMMAR, "Expected `->`.", spans))
42			})
43	}
44}
45
46impl IntoTokens for RArrow {
47	fn into_tokens(self, _root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
48		let Self(minus, gt) = self;
49		tokens.extend([minus.into(), gt.into()])
50	}
51}
52
53/// `..`
54#[derive(Clone)]
55pub struct DotDot(pub Punct, pub Punct);
56
57impl Default for DotDot {
58	fn default() -> Self {
59		Self(
60			Punct::new('.', Spacing::Joint).with_span(Span::mixed_site()),
61			Punct::new('.', Spacing::Alone).with_span(Span::mixed_site()),
62		)
63	}
64}
65
66/// `..`
67impl PeekFrom for DotDot {
68	fn peek_from(input: &Input) -> bool {
69		input.peek(|tts, mut rest| {
70			matches!(tts, [TokenTree::Punct(dot0), TokenTree::Punct(dot1)]
71			if dot0.as_char() == '.'
72				&& dot0.spacing() == Spacing::Joint
73				&& dot1.as_char() == '.'
74				&& (dot1.spacing() == Spacing::Alone || !matches!(rest.next(), Some(TokenTree::Punct(next_punct)) if matches!(next_punct.as_char(), '.' | '='))))
75		})
76	}
77}
78
79impl PopFrom for DotDot {
80	fn pop_from(input: &mut Input, errors: &mut Errors) -> Result<Self, ()> {
81		input
82			.pop_or_replace(|tts, rest| match tts {
83				[TokenTree::Punct(dot0), TokenTree::Punct(dot1)]
84					if dot0.as_char() == '.'
85						&& dot0.spacing() == Spacing::Joint
86						&& dot1.as_char() == '.'
87						&& (dot1.spacing() == Spacing::Alone || !matches!(rest.front(), Some(TokenTree::Punct(next_punct)) if matches!(next_punct.as_char(), '.' | '=')))
88					=> { Ok(Self(dot0, dot1)) }
89				other => Err(other),
90			})
91			.map_err(|spans| {
92				errors.push(Error::new(ErrorPriority::GRAMMAR, "Expected `..`.", spans))
93			})
94	}
95}
96
97impl IntoTokens for DotDot {
98	fn into_tokens(self, _root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
99		let Self(dot0, dot1) = self;
100		tokens.extend([dot0.into(), dot1.into()])
101	}
102}
103
104/// `;`
105#[derive(Clone)]
106pub struct Semi(pub Punct);
107
108impl Default for Semi {
109	fn default() -> Self {
110		Self(Punct::new(';', Spacing::Alone).with_span(Span::mixed_site()))
111	}
112}
113
114/// `;`
115impl PeekFrom for Semi {
116	fn peek_from(input: &Input) -> bool {
117		matches!(
118			input.front(),
119			Some(TokenTree::Punct(semi)) if semi.as_char() == ';',
120		)
121	}
122}
123
124impl PopFrom for Semi {
125	fn pop_from(input: &mut Input, errors: &mut Errors) -> Result<Self, ()> {
126		input
127			.pop_or_replace(|tts, _| match tts {
128				[TokenTree::Punct(semi)] if semi.as_char() == ';' => Ok(Self(semi)),
129				other => Err(other),
130			})
131			.map_err(|spans| {
132				errors.push(Error::new(ErrorPriority::GRAMMAR, "Expected `;`.", spans))
133			})
134	}
135}
136
137impl IntoTokens for Semi {
138	fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
139		self.0.into_tokens(root, tokens)
140	}
141}
142
143/// `,`
144#[derive(Clone)]
145pub struct Comma(pub Punct);
146
147impl Default for Comma {
148	fn default() -> Self {
149		Self(Punct::new(',', Spacing::Alone).with_span(Span::mixed_site()))
150	}
151}
152
153/// `,`
154impl PeekFrom for Comma {
155	fn peek_from(input: &Input) -> bool {
156		matches!(
157			input.front(),
158			Some(TokenTree::Punct(comma)) if comma.as_char() == ',',
159		)
160	}
161}
162
163impl PopFrom for Comma {
164	fn pop_from(input: &mut Input, errors: &mut Errors) -> Result<Self, ()> {
165		input
166			.pop_or_replace(|tts, _| match tts {
167				[TokenTree::Punct(comma)] if comma.as_char() == ',' => Ok(Self(comma)),
168				other => Err(other),
169			})
170			.map_err(|spans| {
171				errors.push(Error::new(ErrorPriority::GRAMMAR, "Expected `,`.", spans))
172			})
173	}
174}
175
176impl IntoTokens for Comma {
177	fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
178		self.0.into_tokens(root, tokens)
179	}
180}
181
182/// `|`
183#[derive(Clone)]
184pub struct Or(pub Punct);
185
186impl Default for Or {
187	fn default() -> Self {
188		Self(Punct::new('|', Spacing::Alone).with_span(Span::mixed_site()))
189	}
190}
191
192/// `|`
193impl PeekFrom for Or {
194	fn peek_from(input: &Input) -> bool {
195		matches!(
196			input.front(),
197			Some(TokenTree::Punct(or)) if or.as_char() == '|' && or.spacing() == Spacing::Alone,
198		)
199	}
200}
201
202impl PopFrom for Or {
203	fn pop_from(input: &mut Input, errors: &mut Errors) -> Result<Self, ()> {
204		input
205			.pop_or_replace(|tts, _| match tts {
206				[TokenTree::Punct(or)] if or.as_char() == '|' && or.spacing() == Spacing::Alone => {
207					Ok(Self(or))
208				}
209				other => Err(other),
210			})
211			.map_err(|spans| {
212				errors.push(Error::new(ErrorPriority::GRAMMAR, "Expected `|`.", spans))
213			})
214	}
215}
216
217impl IntoTokens for Or {
218	fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
219		self.0.into_tokens(root, tokens)
220	}
221}
222
223/// `.`
224#[derive(Clone)]
225pub struct Dot(pub Punct);
226
227impl Default for Dot {
228	fn default() -> Self {
229		Self(Punct::new('.', Spacing::Alone).with_span(Span::mixed_site()))
230	}
231}
232
233/// `.`
234impl PeekFrom for Dot {
235	fn peek_from(input: &Input) -> bool {
236		matches!(
237			input.front(),
238			Some(TokenTree::Punct(dot)) if dot.as_char() == '.' && dot.spacing() == Spacing::Alone,
239		)
240	}
241}
242
243impl PopFrom for Dot {
244	fn pop_from(input: &mut Input, errors: &mut Errors) -> Result<Self, ()> {
245		input
246			.pop_or_replace(|tts, _| match tts {
247				[TokenTree::Punct(dot)]
248					if dot.as_char() == '.' && dot.spacing() == Spacing::Alone =>
249				{
250					Ok(Self(dot))
251				}
252				other => Err(other),
253			})
254			.map_err(|spans| {
255				errors.push(Error::new(ErrorPriority::GRAMMAR, "Expected `.`.", spans))
256			})
257	}
258}
259
260impl IntoTokens for Dot {
261	fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
262		self.0.into_tokens(root, tokens)
263	}
264}
265
266/// `:`
267#[derive(Clone)]
268pub struct Colon(pub Punct);
269
270impl Default for Colon {
271	fn default() -> Self {
272		Self(Punct::new(':', Spacing::Alone).with_span(Span::mixed_site()))
273	}
274}
275
276/// `:`
277impl PeekFrom for Colon {
278	fn peek_from(input: &Input) -> bool {
279		matches!(
280			input.front(),
281			Some(TokenTree::Punct(colon)) if colon.as_char() == ':' && colon.spacing() == Spacing::Alone,
282		)
283	}
284}
285
286impl PopFrom for Colon {
287	fn pop_from(input: &mut Input, errors: &mut Errors) -> Result<Self, ()> {
288		input
289			.pop_or_replace(|tts, _| match tts {
290				[TokenTree::Punct(colon)]
291					if colon.as_char() == ':' && colon.spacing() == Spacing::Alone =>
292				{
293					Ok(Self(colon))
294				}
295				other => Err(other),
296			})
297			.map_err(|spans| {
298				errors.push(Error::new(ErrorPriority::GRAMMAR, "Expected `:`.", spans))
299			})
300	}
301}
302
303impl IntoTokens for Colon {
304	fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
305		self.0.into_tokens(root, tokens)
306	}
307}
308
309macro_rules! ident_token {
310	($name:ident = $str:literal) => {
311		#[doc = concat!("`", $str, "`")]
312		#[derive(Clone)]
313		pub struct $name(pub Ident);
314
315		#[doc = concat!("`", $str, "`")]
316		impl PeekFrom for $name {
317			fn peek_from(input: &Input) -> bool {
318				matches!(input.front(), Some(TokenTree::Ident(ident)) if ident == $str)
319			}
320		}
321
322		impl PopFrom for $name {
323			fn pop_from(input: &mut Input, errors: &mut Errors) -> Result<Self, ()>
324			where
325				Self: Sized,
326			{
327				input
328					.pop_or_replace(|t, _| match t {
329						[TokenTree::Ident(ident)] if ident == $str => Ok(Self(ident)),
330						other => Err(other),
331					})
332					.map_err(|spans| {
333						errors.push(Error::new(ErrorPriority::TOKEN, concat!("Expected `", $str, "`."), spans))
334					})
335			}
336		}
337
338		impl IntoTokens for $name {
339			fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
340				self.0.into_tokens(root, tokens)
341			}
342		}
343	};
344}
345
346ident_token!(As = "as");
347ident_token!(Async = "async");
348ident_token!(Await = "await");
349ident_token!(Box = "box");
350ident_token!(Const = "const");
351ident_token!(For = "for");
352ident_token!(In = "in");
353ident_token!(Let = "let");
354ident_token!(Pub = "pub");
355ident_token!(SelfLowercase = "self");
356ident_token!(Struct = "struct");