1use std::{
2 collections::{HashMap, HashSet},
3 error::Error,
4 fmt,
5};
6
7use crate::{
8 Keyword, LexerError, ScriptResolver, SourceError, SourceFile, SourceId, SourceLoadOptions,
9 SourceMap, Token, TokenKind, lex_source,
10};
11
12#[derive(#[automatically_derived]
impl ::core::fmt::Debug for IncludeEdge {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f, "IncludeEdge",
"from", &self.from, "to", &self.to, "include_name",
&&self.include_name)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for IncludeEdge {
#[inline]
fn clone(&self) -> IncludeEdge {
IncludeEdge {
from: ::core::clone::Clone::clone(&self.from),
to: ::core::clone::Clone::clone(&self.to),
include_name: ::core::clone::Clone::clone(&self.include_name),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for IncludeEdge {
#[inline]
fn eq(&self, other: &IncludeEdge) -> bool {
self.from == other.from && self.to == other.to &&
self.include_name == other.include_name
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for IncludeEdge {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<SourceId>;
let _: ::core::cmp::AssertParamIsEq<String>;
}
}Eq)]
14pub struct IncludeEdge {
15 pub from: SourceId,
17 pub to: SourceId,
19 pub include_name: String,
21}
22
23#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SourceBundle {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field4_finish(f, "SourceBundle",
"source_map", &self.source_map, "root_id", &self.root_id,
"source_order", &self.source_order, "include_edges",
&&self.include_edges)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for SourceBundle {
#[inline]
fn clone(&self) -> SourceBundle {
SourceBundle {
source_map: ::core::clone::Clone::clone(&self.source_map),
root_id: ::core::clone::Clone::clone(&self.root_id),
source_order: ::core::clone::Clone::clone(&self.source_order),
include_edges: ::core::clone::Clone::clone(&self.include_edges),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for SourceBundle {
#[inline]
fn eq(&self, other: &SourceBundle) -> bool {
self.source_map == other.source_map && self.root_id == other.root_id
&& self.source_order == other.source_order &&
self.include_edges == other.include_edges
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for SourceBundle {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<SourceMap>;
let _: ::core::cmp::AssertParamIsEq<SourceId>;
let _: ::core::cmp::AssertParamIsEq<Vec<SourceId>>;
let _: ::core::cmp::AssertParamIsEq<Vec<IncludeEdge>>;
}
}Eq)]
25pub struct SourceBundle {
26 pub source_map: SourceMap,
28 pub root_id: SourceId,
30 pub source_order: Vec<SourceId>,
32 pub include_edges: Vec<IncludeEdge>,
34}
35
36#[derive(#[automatically_derived]
impl ::core::fmt::Debug for MacroDefinition {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field4_finish(f,
"MacroDefinition", "name", &self.name, "replacement",
&self.replacement, "source_id", &self.source_id, "line",
&&self.line)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for MacroDefinition {
#[inline]
fn clone(&self) -> MacroDefinition {
MacroDefinition {
name: ::core::clone::Clone::clone(&self.name),
replacement: ::core::clone::Clone::clone(&self.replacement),
source_id: ::core::clone::Clone::clone(&self.source_id),
line: ::core::clone::Clone::clone(&self.line),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for MacroDefinition {
#[inline]
fn eq(&self, other: &MacroDefinition) -> bool {
self.name == other.name && self.replacement == other.replacement &&
self.source_id == other.source_id && self.line == other.line
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for MacroDefinition {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<String>;
let _: ::core::cmp::AssertParamIsEq<Vec<Token>>;
let _: ::core::cmp::AssertParamIsEq<SourceId>;
let _: ::core::cmp::AssertParamIsEq<usize>;
}
}Eq)]
38pub struct MacroDefinition {
39 pub name: String,
41 pub replacement: Vec<Token>,
43 pub source_id: SourceId,
45 pub line: usize,
47}
48
49#[derive(#[automatically_derived]
impl ::core::fmt::Debug for PreprocessedSource {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"PreprocessedSource", "tokens", &self.tokens, "defines",
&&self.defines)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for PreprocessedSource {
#[inline]
fn clone(&self) -> PreprocessedSource {
PreprocessedSource {
tokens: ::core::clone::Clone::clone(&self.tokens),
defines: ::core::clone::Clone::clone(&self.defines),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for PreprocessedSource {
#[inline]
fn eq(&self, other: &PreprocessedSource) -> bool {
self.tokens == other.tokens && self.defines == other.defines
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for PreprocessedSource {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<Vec<Token>>;
let _: ::core::cmp::AssertParamIsEq<Vec<MacroDefinition>>;
}
}Eq)]
51pub struct PreprocessedSource {
52 pub tokens: Vec<Token>,
54 pub defines: Vec<MacroDefinition>,
56}
57
58#[derive(#[automatically_derived]
impl ::core::fmt::Debug for PreprocessError {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
PreprocessError::Source(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Source",
&__self_0),
PreprocessError::Lex(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Lex",
&__self_0),
}
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for PreprocessError {
#[inline]
fn clone(&self) -> PreprocessError {
match self {
PreprocessError::Source(__self_0) =>
PreprocessError::Source(::core::clone::Clone::clone(__self_0)),
PreprocessError::Lex(__self_0) =>
PreprocessError::Lex(::core::clone::Clone::clone(__self_0)),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for PreprocessError {
#[inline]
fn eq(&self, other: &PreprocessError) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(PreprocessError::Source(__self_0),
PreprocessError::Source(__arg1_0)) => __self_0 == __arg1_0,
(PreprocessError::Lex(__self_0),
PreprocessError::Lex(__arg1_0)) => __self_0 == __arg1_0,
_ => unsafe { ::core::intrinsics::unreachable() }
}
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for PreprocessError {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<SourceError>;
let _: ::core::cmp::AssertParamIsEq<LexerError>;
}
}Eq)]
60pub enum PreprocessError {
61 Source(SourceError),
63 Lex(LexerError),
65}
66
67impl fmt::Display for PreprocessError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 Self::Source(error) => error.fmt(f),
71 Self::Lex(error) => error.fmt(f),
72 }
73 }
74}
75
76impl Error for PreprocessError {}
77
78impl From<SourceError> for PreprocessError {
79 fn from(value: SourceError) -> Self {
80 Self::Source(value)
81 }
82}
83
84impl From<LexerError> for PreprocessError {
85 fn from(value: LexerError) -> Self {
86 Self::Lex(value)
87 }
88}
89
90pub fn load_source_bundle<R: ScriptResolver + ?Sized>(
96 resolver: &R,
97 root_name: &str,
98 options: SourceLoadOptions,
99) -> Result<SourceBundle, PreprocessError> {
100 let mut loader = SourceBundleLoader::new(resolver, options);
101 let root_id = loader.load_script(root_name)?;
102 Ok(SourceBundle {
103 source_map: loader.source_map,
104 root_id,
105 source_order: loader.source_order,
106 include_edges: loader.include_edges,
107 })
108}
109
110pub fn preprocess_source_bundle(
116 bundle: &SourceBundle,
117) -> Result<PreprocessedSource, PreprocessError> {
118 let mut preprocessor = BundlePreprocessor::new(bundle);
119 preprocessor.expand_source(bundle.root_id)?;
120 preprocessor.finish(bundle.root_id)
121}
122
123struct SourceBundleLoader<'a, R: ScriptResolver + ?Sized> {
124 resolver: &'a R,
125 options: SourceLoadOptions,
126 source_map: SourceMap,
127 source_order: Vec<SourceId>,
128 include_edges: Vec<IncludeEdge>,
129 active_stack: Vec<String>,
130}
131
132impl<'a, R: ScriptResolver + ?Sized> SourceBundleLoader<'a, R> {
133 fn new(resolver: &'a R, options: SourceLoadOptions) -> Self {
134 Self {
135 resolver,
136 options,
137 source_map: SourceMap::new(),
138 source_order: Vec::new(),
139 include_edges: Vec::new(),
140 active_stack: Vec::new(),
141 }
142 }
143
144 fn load_script(&mut self, script_name: &str) -> Result<SourceId, PreprocessError> {
145 if let Some(existing) = self.source_map.get_by_name(script_name) {
146 return Ok(existing.id);
147 }
148 if self.active_stack.len() >= self.options.max_include_depth {
149 return Err(SourceError::include_too_many_levels(
150 script_name,
151 self.options.max_include_depth,
152 )
153 .into());
154 }
155
156 let contents = self
157 .resolver
158 .resolve_script_bytes(script_name, self.options.res_type)?
159 .filter(|bytes| !bytes.is_empty())
160 .ok_or_else(|| SourceError::file_not_found(script_name))?;
161
162 let normalized = script_name.to_ascii_lowercase();
163 if self.active_stack.contains(&normalized) {
164 return Err(SourceError::include_recursive(script_name).into());
165 }
166
167 let source_id = self.source_map.next_id();
168 let source_file = SourceFile::new(source_id, script_name, contents);
169 let include_names = scan_include_names(&source_file)?;
170 self.source_map.insert_file(source_file);
171 self.source_order.push(source_id);
172 self.active_stack.push(normalized);
173
174 for include_name in include_names {
175 let child_id = self.load_script(&include_name)?;
176 self.include_edges.push(IncludeEdge {
177 from: source_id,
178 to: child_id,
179 include_name,
180 });
181 }
182
183 self.active_stack.pop();
184 Ok(source_id)
185 }
186}
187
188fn scan_include_names(source_file: &SourceFile) -> Result<Vec<String>, LexerError> {
189 let tokens = lex_source(source_file)?;
190 let mut includes = Vec::new();
191 let mut index = 0;
192 while index < tokens.len() {
193 if tokens
194 .get(index)
195 .is_some_and(|token| token.kind == TokenKind::Keyword(Keyword::Include))
196 && let Some(argument) = tokens.get(index + 1)
197 && argument.kind == TokenKind::String
198 {
199 includes.push(argument.text.clone());
200 index += 2;
201 continue;
202 }
203 index += 1;
204 }
205 Ok(includes)
206}
207
208struct BundlePreprocessor<'a> {
209 bundle: &'a SourceBundle,
210 defines: HashMap<String, MacroDefinition>,
211 define_order: Vec<MacroDefinition>,
212 expanded_files: HashSet<SourceId>,
213 tokens: Vec<Token>,
214}
215
216impl<'a> BundlePreprocessor<'a> {
217 fn new(bundle: &'a SourceBundle) -> Self {
218 Self {
219 bundle,
220 defines: HashMap::new(),
221 define_order: Vec::new(),
222 expanded_files: HashSet::new(),
223 tokens: Vec::new(),
224 }
225 }
226
227 fn finish(mut self, root_id: SourceId) -> Result<PreprocessedSource, PreprocessError> {
228 let root = self
229 .bundle
230 .source_map
231 .get(root_id)
232 .ok_or_else(|| SourceError::file_not_found("root"))?;
233 self.tokens.push(Token::new(
234 TokenKind::Eof,
235 crate::Span::new(root_id, root.len(), root.len()),
236 "",
237 ));
238 Ok(PreprocessedSource {
239 tokens: self.tokens,
240 defines: self.define_order,
241 })
242 }
243
244 fn expand_source(&mut self, source_id: SourceId) -> Result<(), PreprocessError> {
245 if !self.expanded_files.insert(source_id) {
246 return Ok(());
247 }
248
249 let source = self
250 .bundle
251 .source_map
252 .get(source_id)
253 .ok_or_else(|| SourceError::file_not_found(source_id.to_string()))?;
254 let tokens = lex_source(source)?;
255 let mut index = 0;
256
257 while index < tokens.len() {
258 let Some(token) = tokens.get(index) else {
259 break;
260 };
261 if token.kind == TokenKind::Eof {
262 break;
263 }
264
265 let line = token_line(source, token).ok_or_else(|| LexerError {
266 code: crate::CompilerErrorCode::UnknownStateInCompiler,
267 span: crate::Span::new(source.id, token.span.start, token.span.end),
268 message: "failed to resolve token line during preprocessing".to_string(),
269 })?;
270 let line_end = next_line_index(source, &tokens, index, line);
271
272 if token.kind == TokenKind::Keyword(Keyword::Include)
273 && let Some(argument) = tokens.get(index + 1)
274 && argument.kind == TokenKind::String
275 && token_line(source, argument) == Some(line)
276 && let Some(include) = self.bundle.source_map.get_by_name(&argument.text)
277 {
278 self.expand_source(include.id)?;
279 index = line_end;
280 continue;
281 }
282
283 if token.kind == TokenKind::Keyword(Keyword::Define) {
284 let line_tokens = tokens.get(index..line_end).unwrap_or(&[]);
285 self.capture_define(source, line_tokens, line);
286 index = line_end;
287 continue;
288 }
289
290 for token in tokens.get(index..line_end).unwrap_or(&[]) {
291 self.expand_token(token, &mut Vec::new());
292 }
293 index = line_end;
294 }
295
296 Ok(())
297 }
298
299 fn capture_define(&mut self, source: &SourceFile, line_tokens: &[Token], line: usize) {
300 let Some(name_token) = line_tokens.get(1) else {
301 return;
302 };
303 if name_token.kind != TokenKind::Identifier {
304 return;
305 }
306
307 let replacement = line_tokens
308 .iter()
309 .skip(2)
310 .filter(|token| token.kind != TokenKind::Eof)
311 .cloned()
312 .collect::<Vec<_>>();
313 let definition = MacroDefinition {
314 name: name_token.text.clone(),
315 replacement,
316 source_id: source.id,
317 line,
318 };
319 self.defines
320 .insert(definition.name.clone(), definition.clone());
321 self.define_order.push(definition);
322 }
323
324 fn expand_token(&mut self, token: &Token, active: &mut Vec<String>) {
325 if token.kind == TokenKind::Identifier
326 && let Some(definition) = self.defines.get(&token.text).cloned()
327 && !active.iter().any(|name| name == &definition.name)
328 {
329 active.push(definition.name.clone());
330 for replacement in definition.replacement {
331 let rewritten = Token::new(replacement.kind, token.span, replacement.text);
335 self.expand_token(&rewritten, active);
336 }
337 active.pop();
338 return;
339 }
340
341 self.tokens.push(token.clone());
342 }
343}
344
345fn token_line(source: &SourceFile, token: &Token) -> Option<usize> {
346 source
347 .location(token.span.start)
348 .map(|location| location.line)
349}
350
351fn next_line_index(source: &SourceFile, tokens: &[Token], start: usize, line: usize) -> usize {
352 let mut index = start;
353 while let Some(token) = tokens.get(index) {
354 if token.kind == TokenKind::Eof {
355 break;
356 }
357 if token_line(source, token) != Some(line) {
358 break;
359 }
360 index += 1;
361 }
362 index
363}
364
365#[cfg(test)]
366mod tests {
367 use super::{load_source_bundle, preprocess_source_bundle};
368 use crate::{CompilerErrorCode, InMemoryScriptResolver, Keyword, SourceLoadOptions, TokenKind};
369
370 fn token_pairs(preprocessed: super::PreprocessedSource) -> Vec<(TokenKind, String)> {
371 preprocessed
372 .tokens
373 .into_iter()
374 .map(|token| (token.kind, token.text))
375 .collect::<Vec<_>>()
376 }
377
378 #[test]
379 fn loads_transitive_includes_and_ignores_duplicate_files() {
380 let mut resolver = InMemoryScriptResolver::new();
381 resolver.insert_source(
382 "root",
383 r#"#include "util"
384#include "common"
385#include "util"
386void main() {}"#,
387 );
388 resolver.insert_source(
389 "util",
390 r#"#include "common"
391int UTIL = 1;"#,
392 );
393 resolver.insert_source("common", "int COMMON = 2;");
394
395 let bundle = load_source_bundle(&resolver, "root", SourceLoadOptions::default());
396 let names = bundle.ok().map(|bundle| {
397 bundle
398 .source_order
399 .iter()
400 .filter_map(|id| bundle.source_map.get(*id).map(|file| file.name.clone()))
401 .collect::<Vec<_>>()
402 });
403
404 assert_eq!(
405 names,
406 Some(vec![
407 "root".to_string(),
408 "util".to_string(),
409 "common".to_string(),
410 ])
411 );
412 }
413
414 #[test]
415 fn treats_empty_source_as_file_not_found() {
416 let mut resolver = InMemoryScriptResolver::new();
417 resolver.insert_source("root", r#"#include "empty""#);
418 resolver.insert_source("empty", "");
419
420 let error = load_source_bundle(&resolver, "root", SourceLoadOptions::default()).err();
421 let code = error.and_then(|error| match error {
422 super::PreprocessError::Source(source) => source.code(),
423 super::PreprocessError::Lex(_) => None,
424 });
425
426 assert_eq!(code, Some(CompilerErrorCode::FileNotFound));
427 }
428
429 #[test]
430 fn enforces_include_depth_limit() {
431 let mut resolver = InMemoryScriptResolver::new();
432 resolver.insert_source("root", r#"#include "a""#);
433 resolver.insert_source("a", r#"#include "b""#);
434 resolver.insert_source("b", r#"#include "c""#);
435 resolver.insert_source("c", "void c() {}");
436
437 let error = load_source_bundle(
438 &resolver,
439 "root",
440 SourceLoadOptions {
441 max_include_depth: 2,
442 ..SourceLoadOptions::default()
443 },
444 )
445 .err();
446 let code = error.and_then(|error| match error {
447 super::PreprocessError::Source(source) => source.code(),
448 super::PreprocessError::Lex(_) => None,
449 });
450
451 assert_eq!(code, Some(CompilerErrorCode::IncludeTooManyLevels));
452 }
453
454 #[test]
455 fn resolver_matches_include_names_case_insensitively() {
456 let mut resolver = InMemoryScriptResolver::new();
457 resolver.insert_source("ROOT", r#"#include "Util""#);
458 resolver.insert_source("util", "void util() {}");
459
460 let bundle = load_source_bundle(&resolver, "root", SourceLoadOptions::default());
461 let count = bundle.ok().map(|bundle| bundle.source_map.len());
462
463 assert_eq!(count, Some(2));
464 }
465
466 #[test]
467 fn preprocesses_object_like_defines_with_include_order()
468 -> Result<(), Box<dyn std::error::Error>> {
469 let mut resolver = InMemoryScriptResolver::new();
470 resolver.insert_source(
471 "root",
472 br#"#define VALUE 7
473#include "util"
474int x = VALUE;
475"#,
476 );
477 resolver.insert_source(
478 "util",
479 br#"#define PLUS +
480int y = VALUE PLUS 1;
481"#,
482 );
483
484 let bundle = load_source_bundle(&resolver, "root", SourceLoadOptions::default())?;
485 let pairs = token_pairs(preprocess_source_bundle(&bundle)?);
486
487 assert_eq!(
488 pairs,
489 vec![
490 (TokenKind::Keyword(Keyword::Int), "int".to_string()),
491 (TokenKind::Identifier, "y".to_string()),
492 (TokenKind::Assign, "=".to_string()),
493 (TokenKind::Integer, "7".to_string()),
494 (TokenKind::Plus, "+".to_string()),
495 (TokenKind::Integer, "1".to_string()),
496 (TokenKind::Semicolon, ";".to_string()),
497 (TokenKind::Keyword(Keyword::Int), "int".to_string()),
498 (TokenKind::Identifier, "x".to_string()),
499 (TokenKind::Assign, "=".to_string()),
500 (TokenKind::Integer, "7".to_string()),
501 (TokenKind::Semicolon, ";".to_string()),
502 (TokenKind::Eof, "".to_string()),
503 ]
504 );
505 Ok(())
506 }
507
508 #[test]
509 fn preprocess_define_redefinitions_use_latest_value() -> Result<(), Box<dyn std::error::Error>>
510 {
511 let mut resolver = InMemoryScriptResolver::new();
512 resolver.insert_source(
513 "root",
514 br#"#define VALUE 1
515#define VALUE 2
516int x = VALUE;
517"#,
518 );
519
520 let bundle = load_source_bundle(&resolver, "root", SourceLoadOptions::default())?;
521 let preprocessed = preprocess_source_bundle(&bundle)?;
522 let integers = preprocessed
523 .tokens
524 .into_iter()
525 .filter(|token| token.kind == TokenKind::Integer)
526 .map(|token| token.text)
527 .collect::<Vec<_>>();
528
529 assert_eq!(integers, vec!["2".to_string()]);
530 Ok(())
531 }
532
533 #[test]
534 fn chained_define_expansion_preserves_upstream_literal_token_kinds()
535 -> Result<(), Box<dyn std::error::Error>> {
536 let mut resolver = InMemoryScriptResolver::new();
537 resolver.insert_source(
538 "root",
539 br#"#define BASE 4
540#define VALUE BASE
541int x = VALUE;
542"#,
543 );
544
545 let bundle = load_source_bundle(&resolver, "root", SourceLoadOptions::default())?;
546 let pairs = token_pairs(preprocess_source_bundle(&bundle)?);
547
548 assert_eq!(
549 pairs,
550 vec![
551 (TokenKind::Keyword(Keyword::Int), "int".to_string()),
552 (TokenKind::Identifier, "x".to_string()),
553 (TokenKind::Assign, "=".to_string()),
554 (TokenKind::Integer, "4".to_string()),
555 (TokenKind::Semicolon, ";".to_string()),
556 (TokenKind::Eof, "".to_string()),
557 ]
558 );
559 Ok(())
560 }
561
562 #[test]
563 fn define_visibility_tracks_include_encounter_order() -> Result<(), Box<dyn std::error::Error>>
564 {
565 let mut resolver = InMemoryScriptResolver::new();
566 resolver.insert_source(
567 "root",
568 br#"#define VALUE 1
569#include "util"
570#define VALUE 2
571int root_value = VALUE;
572"#,
573 );
574 resolver.insert_source("util", br#"int util_value = VALUE;"#);
575
576 let bundle = load_source_bundle(&resolver, "root", SourceLoadOptions::default())?;
577 let pairs = token_pairs(preprocess_source_bundle(&bundle)?);
578
579 assert_eq!(
580 pairs,
581 vec![
582 (TokenKind::Keyword(Keyword::Int), "int".to_string()),
583 (TokenKind::Identifier, "util_value".to_string()),
584 (TokenKind::Assign, "=".to_string()),
585 (TokenKind::Integer, "1".to_string()),
586 (TokenKind::Semicolon, ";".to_string()),
587 (TokenKind::Keyword(Keyword::Int), "int".to_string()),
588 (TokenKind::Identifier, "root_value".to_string()),
589 (TokenKind::Assign, "=".to_string()),
590 (TokenKind::Integer, "2".to_string()),
591 (TokenKind::Semicolon, ";".to_string()),
592 (TokenKind::Eof, "".to_string()),
593 ]
594 );
595 Ok(())
596 }
597
598 #[test]
599 fn define_expansion_preserves_keyword_token_kinds() -> Result<(), Box<dyn std::error::Error>> {
600 let mut resolver = InMemoryScriptResolver::new();
601 resolver.insert_source(
602 "root",
603 br#"#define BAD_OBJECT OBJECT_INVALID
604object value = BAD_OBJECT;
605"#,
606 );
607
608 let bundle = load_source_bundle(&resolver, "root", SourceLoadOptions::default())?;
609 let pairs = token_pairs(preprocess_source_bundle(&bundle)?);
610
611 assert_eq!(
612 pairs,
613 vec![
614 (TokenKind::Keyword(Keyword::Object), "object".to_string()),
615 (TokenKind::Identifier, "value".to_string()),
616 (TokenKind::Assign, "=".to_string()),
617 (
618 TokenKind::Keyword(Keyword::ObjectInvalid),
619 "OBJECT_INVALID".to_string(),
620 ),
621 (TokenKind::Semicolon, ";".to_string()),
622 (TokenKind::Eof, "".to_string()),
623 ]
624 );
625 Ok(())
626 }
627}