1use std::{collections::HashMap, iter::FusedIterator, path::PathBuf};
4
5use lang_util::{located::FileIdResolver, FileId};
6
7use crate::{
8 exts::{names::ExtNameAtom, ExtensionSpec, Registry},
9 processor::{
10 event::{self, DirectiveKind, Error, ErrorKind, EventDirective, OutputToken, TokenLike},
11 expand::ExpandLocation,
12 nodes::{Extension, ExtensionBehavior, ExtensionName},
13 },
14 types::{
15 type_names::{TypeNameAtom, TypeNameState},
16 Token, TypeName,
17 },
18};
19
20mod token;
21
22#[derive(Debug, PartialEq)]
23pub enum Event {
24 Error {
25 error: Error,
26 masked: bool,
27 },
28 EnterFile {
29 file_id: FileId,
30 path: PathBuf,
31 canonical_path: PathBuf,
32 },
33 Token {
34 source_token: OutputToken,
35 token_kind: Token,
36 state: TokenState,
37 },
38 Directive {
39 directive: EventDirective,
40 masked: bool,
41 },
42}
43
44pub trait LocatedIterator {
45 fn location(&self) -> &ExpandLocation;
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum TokenState {
50 Masked,
51 Active,
52 Warn(ExtNameAtom),
53}
54
55impl TokenState {
56 fn new(state: Option<TypeNameState>, masked: bool) -> Self {
57 if masked {
58 Self::Masked
59 } else if let Some(state) = state {
60 match state {
61 TypeNameState::WarnType(name) => Self::Warn(name),
62 _ => Self::Active,
63 }
64 } else {
65 Self::Active
66 }
67 }
68
69 pub fn active(self) -> bool {
70 matches!(self, Self::Active | Self::Warn(_))
71 }
72
73 pub fn warn(self) -> bool {
74 matches!(self, Self::Warn(_))
75 }
76}
77
78struct TypeTable<'r> {
79 type_names: HashMap<TypeNameAtom, Option<(ExtNameAtom, ExtensionBehavior)>>,
80 extensions: HashMap<ExtNameAtom, ExtensionBehavior>,
81 registry: &'r Registry,
82 target_vulkan: bool,
83 current_version: u16,
84}
85
86impl<'r> TypeTable<'r> {
87 fn new(registry: &'r Registry, current_version: u16, target_vulkan: bool) -> Self {
88 Self {
89 type_names: Default::default(),
90 extensions: Default::default(),
91 registry,
92 target_vulkan,
93 current_version,
94 }
95 }
96
97 fn is_type_name(&self, name: &TypeNameAtom) -> TypeNameState {
98 if let Some(type_def) = self.type_names.get(name) {
99 match type_def {
100 Some((name, behavior)) if *behavior == ExtensionBehavior::Warn => {
101 TypeNameState::WarnType(name.clone())
102 }
103 _ => TypeNameState::Type,
104 }
105 } else {
106 TypeNameState::Ident
107 }
108 }
109
110 fn promote_type_name(&mut self, name: TypeNameAtom) -> bool {
111 self.type_names.insert(name, None).is_some()
112 }
113
114 fn set_extension_behavior(&mut self, spec: &ExtensionSpec, behavior: ExtensionBehavior) {
115 if behavior == ExtensionBehavior::Disable {
116 for type_name in spec.type_names() {
118 self.type_names.remove(type_name);
119 }
120
121 self.extensions.remove(spec.name());
122 } else {
123 for type_name in spec.type_names() {
125 self.type_names
126 .insert(type_name.clone(), Some((spec.name().clone(), behavior)));
127 }
128
129 self.extensions.insert(spec.name().clone(), behavior);
130 }
131 }
132
133 fn handle_extension(&mut self, extension: &Extension) -> bool {
134 match &extension.name {
135 ExtensionName::All => {
136 if extension.behavior == ExtensionBehavior::Disable
137 || extension.behavior == ExtensionBehavior::Warn
138 {
139 for ext in self.registry.all() {
140 self.set_extension_behavior(ext, extension.behavior);
141 }
142 } else {
143 }
145 }
146 ExtensionName::Specific(name) => {
147 if let Some(spec) = self.registry.get(name) {
148 self.set_extension_behavior(spec, extension.behavior);
149 } else {
150 return false;
151 }
152 }
153 }
154
155 true
156 }
157
158 fn tokenize_single(
159 &self,
160 token: &impl TokenLike,
161 location: &ExpandLocation,
162 ) -> (Token, Option<TypeNameState>, Option<Error>) {
163 let (token_kind, state) =
164 token::token_from_syntax_kind(token, self.current_version, self.target_vulkan, |tn| {
165 self.is_type_name(tn)
166 });
167
168 let error = if let Some(TypeNameState::WarnType(extension)) = &state {
169 Some(
170 Error::builder()
171 .pos(token.text_range())
172 .resolve_file(location)
173 .finish(ErrorKind::warn_ext_use(
174 extension.clone(),
175 match &token_kind {
176 Token::TYPE_NAME(TypeName::OTHER(type_name)) => Some(type_name.clone()),
177 _ => unreachable!(),
178 },
179 token.text_range(),
180 location,
181 )),
182 )
183 } else {
184 None
185 };
186
187 (token_kind, state, error)
188 }
189}
190
191pub trait MaybeToken {
192 fn as_token(&self) -> Option<(&OutputToken, &Token, &TokenState)>;
193
194 fn as_token_kind(&self) -> Option<&Token> {
195 self.as_token().map(|(_, kind, _)| kind)
196 }
197}
198
199impl<T: MaybeToken, E> MaybeToken for Result<T, E> {
200 fn as_token(&self) -> Option<(&OutputToken, &Token, &TokenState)> {
201 self.as_ref().ok().and_then(MaybeToken::as_token)
202 }
203}
204
205impl MaybeToken for Event {
206 fn as_token(&self) -> Option<(&OutputToken, &Token, &TokenState)> {
207 match self {
208 Event::Token {
209 source_token,
210 token_kind,
211 state,
212 } => Some((source_token, token_kind, state)),
213 _ => None,
214 }
215 }
216}
217
218pub struct Tokenizer<'r, I> {
219 inner: I,
220 type_table: TypeTable<'r>,
221 pending_error: Option<Error>,
222}
223
224impl<'r, I: LocatedIterator> Tokenizer<'r, I> {
225 pub fn new(
226 inner: impl IntoIterator<IntoIter = I>,
227 current_version: u16,
228 target_vulkan: bool,
229 registry: &'r Registry,
230 ) -> Self {
231 Self {
232 inner: inner.into_iter(),
233 type_table: TypeTable::new(registry, current_version, target_vulkan),
234 pending_error: None,
235 }
236 }
237
238 pub fn tokenize_single(
239 &self,
240 token: &impl TokenLike,
241 ) -> (Token, Option<TypeNameState>, Option<Error>) {
242 self.type_table
243 .tokenize_single(token, self.inner.location())
244 }
245
246 pub fn promote_type_name(&mut self, name: TypeNameAtom) -> bool {
247 self.type_table.promote_type_name(name)
248 }
249
250 pub fn location(&self) -> &crate::processor::expand::ExpandLocation {
251 self.inner.location()
252 }
253}
254
255impl<'r, E, I: Iterator<Item = Result<event::Event, E>> + LocatedIterator> Iterator
256 for Tokenizer<'r, I>
257{
258 type Item = Result<Event, E>;
259
260 fn next(&mut self) -> Option<Self::Item> {
261 if let Some(error) = self.pending_error.take() {
262 return Some(Ok(Event::Error {
263 error,
264 masked: false,
265 }));
266 }
267
268 self.inner.next().map(|result| match result {
269 Ok(event) => Ok(match event {
270 event::Event::Error { error, masked } => Event::Error { error, masked },
271 event::Event::EnterFile {
272 file_id,
273 path,
274 canonical_path,
275 } => Event::EnterFile {
276 file_id,
277 path,
278 canonical_path,
279 },
280 event::Event::Token { token, masked } => {
281 let (token_kind, state, error) = self.tokenize_single(&token);
282
283 if !masked {
284 self.pending_error = error;
285 }
286
287 Event::Token {
288 source_token: token,
289 token_kind,
290 state: TokenState::new(state, masked),
291 }
292 }
293 event::Event::Directive { directive, masked } => {
294 if !masked {
295 match directive.kind() {
296 DirectiveKind::Version(version) => {
297 self.type_table.current_version = version.number;
298 }
299 DirectiveKind::Extension(extension) => {
300 if !self.type_table.handle_extension(extension) {
301 self.pending_error = Some(
302 Error::builder()
303 .pos(directive.text_range())
304 .resolve_file(self.inner.location())
305 .finish(ErrorKind::unsupported_ext(
306 extension.name.clone(),
307 directive.text_range(),
308 self.inner.location(),
309 )),
310 );
311 }
312 }
313 _ => {}
314 }
315 }
316
317 Event::Directive { directive, masked }
318 }
319 }),
320 Err(err) => Err(err),
321 })
322 }
323}
324
325impl<'r, E, I: Iterator<Item = Result<event::Event, E>> + LocatedIterator> FusedIterator
326 for Tokenizer<'r, I>
327{
328}
329
330impl<'r, I: FileIdResolver> FileIdResolver for Tokenizer<'r, I> {
331 fn resolve(&self, file_id: FileId) -> Option<&std::path::Path> {
332 self.inner.resolve(file_id)
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use lang_util::FileId;
339 use rowan::NodeOrToken;
340
341 use crate::{processor::event::DirectiveKind, types::Token};
342
343 use super::{Event, MaybeToken};
344
345 #[test]
346 fn test_float_constant() {
347 use super::Token::*;
348
349 fn parse(src: &str) -> Vec<super::Token> {
350 crate::processor::str::process(src, crate::processor::ProcessorState::default())
351 .tokenize(100, false, &crate::exts::DEFAULT_REGISTRY)
352 .filter_map(|evt| evt.as_token().map(|(_, kind, _)| kind.clone()))
353 .collect()
354 }
355
356 assert_eq!(parse("1e-34"), &[FLOAT_CONST(1E-34)]);
357 assert_eq!(parse("1e-34f"), &[FLOAT_CONST(1E-34)]);
358 assert_eq!(parse("1E-34f"), &[FLOAT_CONST(1E-34)]);
359 assert_eq!(parse("1e-34F"), &[FLOAT_CONST(1E-34)]);
360 assert_eq!(parse("1E-34F"), &[FLOAT_CONST(1E-34)]);
361 }
362
363 #[test]
364 fn test_hash_ident() {
371 use super::Token::*;
372
373 let inputs = [
374 "#(ident) = hello",
375 "# (ident) = hello",
376 "#(ident.clone()) = hello",
377 "# (ident.clone()) = hello",
378 ];
379 let outputs: [&[Token]; 4] = [
380 &[
381 HASH,
382 LPAREN,
383 IDENT("ident".into()),
384 RPAREN,
385 WS,
386 EQUAL,
387 WS,
388 IDENT("hello".into()),
389 ],
390 &[
391 HASH,
392 WS,
393 LPAREN,
394 IDENT("ident".into()),
395 RPAREN,
396 WS,
397 EQUAL,
398 WS,
399 IDENT("hello".into()),
400 ],
401 &[
402 HASH,
403 LPAREN,
404 IDENT("ident".into()),
405 PERIOD,
406 IDENT("clone".into()),
407 LPAREN,
408 RPAREN,
409 RPAREN,
410 WS,
411 EQUAL,
412 WS,
413 IDENT("hello".into()),
414 ],
415 &[
416 HASH,
417 WS,
418 LPAREN,
419 IDENT("ident".into()),
420 PERIOD,
421 IDENT("clone".into()),
422 LPAREN,
423 RPAREN,
424 RPAREN,
425 WS,
426 EQUAL,
427 WS,
428 IDENT("hello".into()),
429 ],
430 ];
431
432 for (input, output) in inputs.iter().zip(outputs.iter()) {
433 let mut tokens = Vec::new();
434
435 let mut tokenizer =
436 crate::processor::str::process(input, crate::processor::ProcessorState::default())
437 .tokenize(100, false, &crate::exts::DEFAULT_REGISTRY);
438
439 #[allow(clippy::while_let_on_iterator)]
440 while let Some(result) = tokenizer.next() {
441 if let Ok(event) = result {
442 match event {
443 Event::Token { token_kind, .. } => {
444 tokens.push(token_kind);
445 }
446 Event::Directive { directive, .. }
447 if matches!(directive.kind(), DirectiveKind::Invalid(_)) =>
448 {
449 tokens.extend(
451 directive
452 .node
453 .descendants_with_tokens()
454 .filter_map(NodeOrToken::into_token)
455 .map(|token| {
456 tokenizer.tokenize_single(&(&token, FileId::default())).0
457 }),
458 );
459 }
460 _ => {}
461 }
462 }
463 }
464
465 assert_eq!(&tokens, output);
466 }
467 }
468}