1mod condition;
24pub mod macro_;
25mod pptoken;
26
27use crate::diagnostic::{Diagnostic, DiagnosticCode};
28use alloc::borrow::ToOwned;
29use alloc::collections::VecDeque;
30use alloc::format;
31use alloc::string::{String, ToString};
32use alloc::vec::Vec;
33use macro_::{MacroTable, MacroUse};
34use zerodds_idl_cst::{Span, Token, Trivia, TriviaKind, lex};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
38pub struct FileId(pub u32);
39
40#[derive(Debug, Clone)]
42pub struct SourceFile {
43 pub path: String,
45 pub text: String,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct Origin {
56 pub file: FileId,
58 pub span: Span,
60 pub macro_chain: Vec<MacroUse>,
63}
64
65impl Origin {
66 #[must_use]
68 pub const fn literal(file: FileId, span: Span) -> Self {
69 Self {
70 file,
71 span,
72 macro_chain: Vec::new(),
73 }
74 }
75
76 #[must_use]
78 pub fn is_expanded(&self) -> bool {
79 !self.macro_chain.is_empty()
80 }
81}
82
83#[derive(Debug, Clone, Default)]
85pub struct SourceMap {
86 files: Vec<SourceFile>,
87 origins: Vec<Origin>,
88 ranges: Vec<(Span, Origin)>,
92}
93
94impl SourceMap {
95 #[must_use]
97 pub const fn new() -> Self {
98 Self {
99 files: Vec::new(),
100 origins: Vec::new(),
101 ranges: Vec::new(),
102 }
103 }
104
105 pub const ROOT: FileId = FileId(0);
107
108 pub fn add_file(&mut self, path: &str, text: &str) -> FileId {
110 let id = u32::try_from(self.files.len()).unwrap_or(u32::MAX);
111 self.files.push(SourceFile {
112 path: path.to_owned(),
113 text: text.to_owned(),
114 });
115 FileId(id)
116 }
117
118 #[must_use]
120 pub fn path(&self, file: FileId) -> Option<&str> {
121 self.files.get(file.0 as usize).map(|f| f.path.as_str())
122 }
123
124 #[must_use]
126 pub fn text(&self, file: FileId) -> Option<&str> {
127 self.files.get(file.0 as usize).map(|f| f.text.as_str())
128 }
129
130 #[must_use]
132 pub fn origin(&self, index: usize) -> Option<&Origin> {
133 self.origins.get(index)
134 }
135
136 #[must_use]
138 pub fn len(&self) -> usize {
139 self.origins.len()
140 }
141
142 #[must_use]
144 pub fn is_empty(&self) -> bool {
145 self.origins.is_empty()
146 }
147
148 fn push(&mut self, origin: Origin) {
149 self.origins.push(origin);
150 }
151
152 fn push_range(&mut self, expanded: Span, origin: Origin) {
153 self.ranges.push((expanded, origin));
154 }
155
156 #[must_use]
167 pub fn original(&self, expanded: Span) -> Option<(FileId, Span)> {
168 let containing = |at: u32| {
169 self.ranges
170 .partition_point(|(range, _)| range.start <= at)
171 .checked_sub(1)
172 };
173 let first = containing(expanded.start)?;
174 let last = containing(expanded.end.saturating_sub(1).max(expanded.start))
175 .unwrap_or(first)
176 .max(first);
177 let (from, start) = &self.ranges[first];
178 let (to, end) = &self.ranges[last];
179 let start_at = Self::offset_in(*from, start, expanded.start, false);
180 if start.file != end.file {
181 return Some((start.file, Span::new(start_at, start.span.end)));
182 }
183 let end_at = Self::offset_in(*to, end, expanded.end, true).max(start_at);
184 Some((start.file, Span::new(start_at, end_at)))
185 }
186
187 #[must_use]
191 pub fn origin_at(&self, at: u32) -> Option<&Origin> {
192 let index = self
193 .ranges
194 .partition_point(|(range, _)| range.start <= at)
195 .checked_sub(1)?;
196 self.ranges.get(index).map(|(_, origin)| origin)
197 }
198
199 fn offset_in(range: Span, origin: &Origin, at: u32, end: bool) -> u32 {
204 let written =
205 !origin.is_expanded() && range.end - range.start == origin.span.end - origin.span.start;
206 if written {
207 let within = at.clamp(range.start, range.end) - range.start;
208 origin.span.start + within
209 } else if end {
210 origin.span.end
211 } else {
212 origin.span.start
213 }
214 }
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
219pub struct Provenance {
220 pub path: String,
222 pub span: Span,
224 pub macro_chain: Vec<ProvenanceMacro>,
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
230pub struct ProvenanceMacro {
231 pub name: String,
233 pub path: Option<String>,
235 pub at: Span,
237}
238
239pub trait FileLoader {
245 fn load(&self, path: &str, from: Option<&str>) -> Option<(String, String)>;
250}
251
252#[derive(Debug, Clone, Default)]
257pub struct MemoryLoader {
258 files: Vec<(String, String)>,
259}
260
261impl MemoryLoader {
262 #[must_use]
264 pub const fn new() -> Self {
265 Self { files: Vec::new() }
266 }
267
268 #[must_use]
270 pub fn with(mut self, path: &str, text: &str) -> Self {
271 self.files.push((path.to_owned(), text.to_owned()));
272 self
273 }
274}
275
276impl FileLoader for MemoryLoader {
277 fn load(&self, path: &str, _from: Option<&str>) -> Option<(String, String)> {
278 self.files
279 .iter()
280 .find(|(name, _)| name == path)
281 .map(|(name, text)| (name.clone(), text.clone()))
282 }
283}
284
285#[derive(Debug, Clone)]
287pub struct Preprocessed {
288 pub tokens: Vec<Token>,
290 pub text: String,
296 pub map: SourceMap,
298 pub diagnostics: Vec<Diagnostic>,
300}
301
302impl Preprocessed {
303 #[must_use]
309 pub fn provenance(&self, expanded: Span) -> Option<Provenance> {
310 let (file, span) = self.map.original(expanded)?;
311 let path = self.map.path(file)?.to_string();
312 let macro_chain = self
313 .map
314 .origin_at(expanded.start)
315 .map(|origin| {
316 origin
317 .macro_chain
318 .iter()
319 .map(|used| ProvenanceMacro {
320 name: used.name.clone(),
321 path: self.map.path(FileId(used.file)).map(ToString::to_string),
322 at: used.at,
323 })
324 .collect()
325 })
326 .unwrap_or_default();
327 Some(Provenance {
328 path,
329 span,
330 macro_chain,
331 })
332 }
333
334 pub fn locate(&self, diagnostic: &mut Diagnostic) {
351 let path = |file: FileId| {
352 (file != SourceMap::ROOT)
353 .then(|| self.map.path(file).map(ToString::to_string))
354 .flatten()
355 };
356 let primary_file = match self.map.original(diagnostic.primary) {
357 Some((file, span)) => {
358 diagnostic.primary = span;
359 diagnostic.file = path(file);
360 Some(file)
361 }
362 None => None,
363 };
364 for (span, note) in &mut diagnostic.labels {
365 if let Some((file, mapped)) = self.map.original(*span) {
366 *span = mapped;
367 if Some(file) != primary_file {
368 let name = self.map.path(file).unwrap_or("?");
369 *note = format!("{name}: {note}");
370 }
371 }
372 }
373 }
374}
375
376const MAX_INCLUDE_DEPTH: usize = 64;
381
382const MAX_EXPANSION_DEPTH: usize = 256;
385
386const MAX_EXPANDED_TOKENS: usize = 1 << 22;
393
394#[must_use]
396pub fn preprocess(path: &str, source: &str, loader: &dyn FileLoader) -> Preprocessed {
397 preprocess_with(path, source, loader, &[])
398}
399
400#[must_use]
408pub fn preprocess_with(
409 path: &str,
410 source: &str,
411 loader: &dyn FileLoader,
412 defines: &[(String, String)],
413) -> Preprocessed {
414 let mut state = Preprocessor {
415 loader,
416 map: SourceMap::new(),
417 macros: MacroTable::new(),
418 diagnostics: Vec::new(),
419 text: String::new(),
420 tokens: Vec::new(),
421 pending_pragmas: Vec::new(),
422 open: Vec::new(),
423 once: Vec::new(),
424 presumed: Vec::new(),
425 expansion_depth: 0,
426 produced: 0,
427 };
428 for (name, body) in defines {
429 let line = format!("{name} {body}");
430 state.define(&line, Span::default());
431 }
432 state.include(path, source, 0);
433 state.flush_trailing_pragmas();
438 Preprocessed {
439 tokens: state.tokens,
440 text: state.text,
441 map: state.map,
442 diagnostics: state.diagnostics,
443 }
444}
445
446struct Preprocessor<'a> {
447 loader: &'a dyn FileLoader,
448 map: SourceMap,
449 macros: MacroTable,
450 diagnostics: Vec<Diagnostic>,
451 text: String,
452 tokens: Vec<Token>,
453 pending_pragmas: Vec<(String, Trivia, FileId)>,
465 open: Vec<String>,
467 once: Vec<String>,
476 presumed: Vec<(FileId, i64, Option<String>)>,
480 expansion_depth: usize,
482 produced: usize,
484}
485
486#[derive(Debug, Clone)]
488struct PpToken {
489 text: String,
491 space_before: bool,
493 origin: Origin,
495 written: Option<Token>,
499 hide: Vec<String>,
502 painted: bool,
505}
506
507impl PpToken {
508 fn made(text: String, space_before: bool, origin: Origin) -> Self {
509 Self {
510 text,
511 space_before,
512 origin,
513 written: None,
514 hide: Vec::new(),
515 painted: false,
516 }
517 }
518
519 fn is_identifier(&self) -> bool {
520 pptoken::is_identifier(&self.text)
521 }
522}
523
524struct FileCursor<'s> {
527 tokens: Vec<Token>,
528 at: usize,
529 source: &'s str,
530 file: FileId,
531 conditions: Vec<Condition>,
532 depth: usize,
533}
534
535impl FileCursor<'_> {
536 fn keeping(&self) -> bool {
537 self.conditions.iter().all(Condition::keeping)
538 }
539}
540
541enum Sink<'v> {
545 Emit,
546 Collect(&'v mut Vec<PpToken>),
547}
548
549impl Preprocessor<'_> {
550 fn include(&mut self, path: &str, source: &str, depth: usize) {
551 if self.once.iter().any(|seen| seen == path) {
552 return;
553 }
554 if depth > MAX_INCLUDE_DEPTH {
555 self.report(Diagnostic::error(
556 DiagnosticCode::UnterminatedDeclaration,
557 Span::default(),
558 format!("include nesting past {MAX_INCLUDE_DEPTH} levels at `{path}`"),
559 "OMG IDL 4.2 §7.3",
560 ));
561 return;
562 }
563 if self.open.iter().any(|open| open == path) {
564 self.report(Diagnostic::error(
565 DiagnosticCode::UnexpectedToken,
566 Span::default(),
567 format!(
568 "`{path}` includes itself, directly or through {}",
569 self.open.join(" -> ")
570 ),
571 "OMG IDL 4.2 §7.3",
572 ));
573 return;
574 }
575
576 let file = self.map.add_file(path, source);
577 self.open.push(path.to_owned());
578 let mut cursor = FileCursor {
579 tokens: lex(source),
580 at: 0,
581 source,
582 file,
583 conditions: Vec::new(),
584 depth,
585 };
586 let mut queue = VecDeque::new();
587 self.run(&mut queue, Some(&mut cursor), &mut Sink::Emit);
588
589 if !cursor.conditions.is_empty() {
590 self.report(Diagnostic::error(
591 DiagnosticCode::UnterminatedDeclaration,
592 Span::default(),
593 format!(
594 "{} conditional(s) left open in `{path}`",
595 cursor.conditions.len()
596 ),
597 "OMG IDL 4.2 §7.3",
598 ));
599 }
600 let _ = self.open.pop();
601 }
602
603 fn pull(
607 &mut self,
608 queue: &mut VecDeque<PpToken>,
609 cursor: Option<&mut FileCursor<'_>>,
610 ) -> Option<PpToken> {
611 if let Some(token) = queue.pop_front() {
612 return Some(token);
613 }
614 let cursor = cursor?;
615 while cursor.at < cursor.tokens.len() {
616 let index = cursor.at;
617 cursor.at += 1;
618 let leading = cursor.tokens[index].leading.clone();
619 for trivia in &leading {
620 match trivia.kind {
621 TriviaKind::Directive => {
622 let line = trivia.span.slice(cursor.source).unwrap_or("");
623 self.directive(line, trivia.span, cursor);
624 }
625 TriviaKind::LineComment
634 if crate::suffix::is_suffix_comment(
635 trivia.span.slice(cursor.source).unwrap_or(""),
636 ) && cursor.keeping() =>
637 {
638 self.pending_pragmas.push((
639 trivia.span.slice(cursor.source).unwrap_or("").to_string(),
640 Trivia {
641 kind: TriviaKind::LineComment,
642 span: trivia.span,
643 },
644 cursor.file,
645 ));
646 }
647 _ => {}
648 }
649 }
650 let token = &cursor.tokens[index];
651 if token.kind == zerodds_idl_cst::TokenKind::Eof || !cursor.keeping() {
652 continue;
653 }
654 return Some(PpToken {
655 text: token.span.slice(cursor.source).unwrap_or("").to_string(),
656 space_before: !token.leading.is_empty(),
657 origin: Origin::literal(cursor.file, token.span),
658 written: Some(token.clone()),
659 hide: Vec::new(),
660 painted: false,
661 });
662 }
663 None
664 }
665
666 fn run(
676 &mut self,
677 queue: &mut VecDeque<PpToken>,
678 mut cursor: Option<&mut FileCursor<'_>>,
679 sink: &mut Sink<'_>,
680 ) {
681 while let Some(mut token) = self.pull(queue, cursor.as_deref_mut()) {
682 if token.painted || !token.is_identifier() {
683 self.deliver(token, sink);
684 continue;
685 }
686 if token.hide.contains(&token.text) {
687 token.painted = true;
690 self.deliver(token, sink);
691 continue;
692 }
693 if let Some(answer) = self.predefined(&token) {
694 self.deliver(answer, sink);
695 continue;
696 }
697 let Some(found) = self.macros.get(&token.text).cloned() else {
698 self.deliver(token, sink);
699 continue;
700 };
701 let replacement = match &found.params {
702 None => {
703 let mut hide = token.hide.clone();
704 hide.push(token.text.clone());
705 self.substitute(&found, &[], &token, &hide)
706 }
707 Some(params) => {
708 let Some(next) = self.pull(queue, cursor.as_deref_mut()) else {
711 self.deliver(token, sink);
712 continue;
713 };
714 if next.text != "(" {
715 queue.push_front(next);
716 self.deliver(token, sink);
717 continue;
718 }
719 let Some((mut args, closing)) =
720 self.arguments(queue, cursor.as_deref_mut(), &token)
721 else {
722 continue;
723 };
724 if params.is_empty() && args.len() == 1 && args[0].is_empty() {
727 args.clear();
728 }
729 if args.len() != params.len() {
730 self.preprocessing_error(
731 token.origin.span,
732 format!(
733 "`{}` takes {} argument(s) and is given {}",
734 token.text,
735 params.len(),
736 args.len()
737 ),
738 "ISO/IEC 14882:2003 §16.3/4",
739 );
740 continue;
741 }
742 let mut hide: Vec<String> = token
746 .hide
747 .iter()
748 .filter(|name| closing.hide.contains(name))
749 .cloned()
750 .collect();
751 hide.push(token.text.clone());
752 self.substitute(&found, &args, &token, &hide)
753 }
754 };
755 self.produced = self.produced.saturating_add(replacement.len());
756 if self.produced > MAX_EXPANDED_TOKENS {
757 self.preprocessing_error(
758 token.origin.span,
759 format!(
760 "macro expansion produced more than {MAX_EXPANDED_TOKENS} tokens; `{}` is \
761 not expanded further",
762 token.text
763 ),
764 "zerodds-idl-frontend-1.0 §3.4",
765 );
766 return;
767 }
768 for replaced in replacement.into_iter().rev() {
769 queue.push_front(replaced);
770 }
771 }
772 }
773
774 fn arguments(
778 &mut self,
779 queue: &mut VecDeque<PpToken>,
780 mut cursor: Option<&mut FileCursor<'_>>,
781 name: &PpToken,
782 ) -> Option<(Vec<Vec<PpToken>>, PpToken)> {
783 let mut args: Vec<Vec<PpToken>> = alloc::vec![Vec::new()];
784 let mut nesting = 0_usize;
785 while let Some(token) = self.pull(queue, cursor.as_deref_mut()) {
786 match token.text.as_str() {
787 ")" if nesting == 0 => return Some((args, token)),
788 "," if nesting == 0 => args.push(Vec::new()),
789 "(" | ")" => {
790 if token.text == "(" {
791 nesting += 1;
792 } else {
793 nesting -= 1;
794 }
795 if let Some(last) = args.last_mut() {
796 last.push(token);
797 }
798 }
799 _ => {
800 if let Some(last) = args.last_mut() {
801 last.push(token);
802 }
803 }
804 }
805 }
806 self.preprocessing_error(
807 name.origin.span,
808 format!(
809 "the invocation of `{}` is not closed before the end of the input",
810 name.text
811 ),
812 "ISO/IEC 14882:2003 §16.3/10",
813 );
814 None
815 }
816
817 fn substitute(
821 &mut self,
822 found: ¯o_::Macro,
823 args: &[Vec<PpToken>],
824 invocation: &PpToken,
825 hide: &[String],
826 ) -> Vec<PpToken> {
827 enum Item {
828 Token(PpToken),
829 Placemarker,
832 Paste,
833 }
834 let mut chain = invocation.origin.macro_chain.clone();
835 chain.push(MacroUse {
836 name: invocation.text.clone(),
837 at: invocation.origin.span,
838 file: invocation.origin.file.0,
839 });
840 let origin = Origin {
841 file: invocation.origin.file,
842 span: invocation.origin.span,
843 macro_chain: chain.clone(),
844 };
845 let params: &[String] = found.params.as_deref().unwrap_or(&[]);
846 let function_like = found.params.is_some();
847 let body = &found.body;
848 let mut expanded: Vec<Option<Vec<PpToken>>> = alloc::vec![None; args.len()];
849 let mut items: Vec<Item> = Vec::new();
850 let mut index = 0;
851 while index < body.len() {
852 let piece = &body[index];
853 let parameter = |text: &str| params.iter().position(|param| param == text);
854 if function_like && piece.text == "#" {
855 if let Some(slot) = body.get(index + 1).and_then(|next| parameter(&next.text)) {
856 items.push(Item::Token(PpToken::made(
857 stringize(&args[slot]),
858 piece.space_before,
859 origin.clone(),
860 )));
861 index += 2;
862 continue;
863 }
864 }
865 if piece.text == "##" {
866 items.push(Item::Paste);
867 index += 1;
868 continue;
869 }
870 if let Some(slot) = parameter(&piece.text).filter(|_| function_like) {
871 let pasted = body.get(index + 1).is_some_and(|next| next.text == "##")
876 || (index > 0 && body[index - 1].text == "##");
877 let tokens = if pasted {
878 args[slot].clone()
879 } else {
880 if expanded[slot].is_none() {
881 expanded[slot] = Some(self.expand_isolated(args[slot].clone()));
882 }
883 expanded[slot].clone().unwrap_or_default()
884 };
885 if tokens.is_empty() {
886 items.push(Item::Placemarker);
887 }
888 for (position, mut token) in tokens.into_iter().enumerate() {
889 if position == 0 {
890 token.space_before = piece.space_before;
891 }
892 let mut inner = chain.clone();
893 inner.extend(token.origin.macro_chain);
894 token.origin.macro_chain = inner;
895 items.push(Item::Token(token));
896 }
897 index += 1;
898 continue;
899 }
900 items.push(Item::Token(PpToken::made(
901 piece.text.clone(),
902 piece.space_before,
903 origin.clone(),
904 )));
905 index += 1;
906 }
907
908 let mut joined: Vec<Item> = Vec::new();
911 let mut items = items.into_iter();
912 while let Some(item) = items.next() {
913 if !matches!(item, Item::Paste) {
914 joined.push(item);
915 continue;
916 }
917 let left = joined.pop();
918 let right = items.next();
919 match (left, right) {
920 (Some(Item::Placemarker), Some(right)) => joined.push(right),
921 (Some(left), Some(Item::Placemarker)) => joined.push(left),
922 (Some(Item::Token(left)), Some(Item::Token(right))) => {
923 let text = format!("{}{}", left.text, right.text);
924 match pptoken::tokenize(&text) {
925 Ok(pieces) if pieces.len() == 1 => {
926 joined.push(Item::Token(PpToken::made(
927 text,
928 left.space_before,
929 origin.clone(),
930 )));
931 }
932 _ => {
933 self.preprocessing_error(
934 invocation.origin.span,
935 format!(
936 "pasting `{}` and `{}` gives `{text}`, which is not one \
937 preprocessing token",
938 left.text, right.text
939 ),
940 "ISO/IEC 14882:2003 §16.3.3/3",
941 );
942 joined.push(Item::Token(left));
943 joined.push(Item::Token(right));
944 }
945 }
946 }
947 (left, right) => {
950 joined.extend(left);
951 joined.extend(right);
952 }
953 }
954 }
955 joined
956 .into_iter()
957 .filter_map(|item| match item {
958 Item::Token(mut token) => {
959 for name in hide {
960 if !token.hide.contains(name) {
961 token.hide.push(name.clone());
962 }
963 }
964 Some(token)
965 }
966 Item::Placemarker | Item::Paste => None,
967 })
968 .collect()
969 }
970
971 fn expand_isolated(&mut self, tokens: Vec<PpToken>) -> Vec<PpToken> {
978 if self.expansion_depth >= MAX_EXPANSION_DEPTH {
979 if let Some(first) = tokens.first() {
980 self.preprocessing_error(
981 first.origin.span,
982 format!(
983 "macro arguments nest past {MAX_EXPANSION_DEPTH} levels; this one is not \
984 expanded"
985 ),
986 "zerodds-idl-frontend-1.0 §3.4",
987 );
988 }
989 return tokens;
990 }
991 self.expansion_depth += 1;
992 let mut queue: VecDeque<PpToken> = tokens.into();
993 let mut out = Vec::new();
994 self.run(&mut queue, None, &mut Sink::Collect(&mut out));
995 self.expansion_depth -= 1;
996 out
997 }
998
999 fn predefined(&self, token: &PpToken) -> Option<PpToken> {
1001 let file = token.origin.file;
1002 let presumed = self.presumed.iter().rev().find(|(of, _, _)| *of == file);
1003 let text = match token.text.as_str() {
1004 "__LINE__" => {
1005 let physical = self
1006 .map
1007 .text(file)
1008 .map_or(1, |source| line_of(source, token.origin.span.start));
1009 let delta = presumed.map_or(0, |(_, delta, _)| *delta);
1010 format!("{}", i64::from(physical) + delta)
1011 }
1012 "__FILE__" => {
1013 let name = presumed
1014 .and_then(|(_, _, name)| name.clone())
1015 .or_else(|| self.map.path(file).map(ToOwned::to_owned))
1016 .unwrap_or_default();
1017 quote(&name)
1018 }
1019 _ => return None,
1020 };
1021 let mut answer = PpToken::made(text, token.space_before, token.origin.clone());
1022 answer.hide.clone_from(&token.hide);
1023 Some(answer)
1024 }
1025
1026 fn deliver(&mut self, token: PpToken, sink: &mut Sink<'_>) {
1028 match sink {
1029 Sink::Collect(list) => list.push(token),
1030 Sink::Emit => self.emit(token),
1031 }
1032 }
1033
1034 fn emit(&mut self, token: PpToken) {
1041 if let Some(written) = &token.written {
1042 let written = written.clone();
1043 self.push_text(written.kind, &token.text, &written, token.origin);
1044 return;
1045 }
1046 let lexed = lex(&token.text);
1047 let mut significant = lexed
1048 .iter()
1049 .filter(|lexed| lexed.kind != zerodds_idl_cst::TokenKind::Eof);
1050 match (significant.next(), significant.next()) {
1051 (Some(only), None) if only.leading.is_empty() => {
1052 let only = only.clone();
1053 self.push_text(only.kind, &token.text, &only, token.origin);
1054 }
1055 _ => {
1056 self.preprocessing_error(
1057 token.origin.span,
1058 format!(
1059 "macro expansion produced `{}`, which is not an IDL token",
1060 token.text
1061 ),
1062 "OMG IDL 4.2 §7.2",
1063 );
1064 let error = Token {
1065 kind: zerodds_idl_cst::TokenKind::Error,
1066 span: Span::default(),
1067 leading: Vec::new(),
1068 value: zerodds_idl_cst::TokenValue::None,
1069 };
1070 self.push_text(error.kind, &token.text, &error, token.origin);
1071 }
1072 }
1073 }
1074
1075 fn report(&mut self, mut diagnostic: Diagnostic) {
1078 if self.open.len() > 1 {
1079 diagnostic.file = self.open.last().cloned();
1080 }
1081 self.diagnostics.push(diagnostic);
1082 }
1083
1084 fn preprocessing_error(&mut self, span: Span, message: String, rule: &'static str) {
1085 self.report(Diagnostic::error(
1086 DiagnosticCode::Preprocessing,
1087 span,
1088 message,
1089 rule,
1090 ));
1091 }
1092
1093 fn push_text(
1096 &mut self,
1097 kind: zerodds_idl_cst::TokenKind,
1098 text: &str,
1099 original: &Token,
1100 origin: Origin,
1101 ) {
1102 if !self.text.is_empty() {
1107 self.text.push(' ');
1108 }
1109 #[allow(clippy::cast_possible_truncation)]
1110 let start = self.text.len() as u32;
1111 self.text.push_str(text);
1112 #[allow(clippy::cast_possible_truncation)]
1113 let end = self.text.len() as u32;
1114 self.map.push_range(Span::new(start, end), origin.clone());
1118 let leading = core::mem::take(&mut self.pending_pragmas)
1119 .into_iter()
1120 .map(|(text, held, file)| {
1121 self.text.push(' ');
1122 #[allow(clippy::cast_possible_truncation)]
1123 let from = self.text.len() as u32;
1124 self.text.push_str(&text);
1125 #[allow(clippy::cast_possible_truncation)]
1126 let to = self.text.len() as u32;
1127 self.map
1128 .push_range(Span::new(from, to), Origin::literal(file, held.span));
1129 Trivia {
1130 kind: held.kind,
1131 span: Span::new(from, to),
1132 }
1133 })
1134 .collect();
1135 self.tokens.push(Token {
1136 kind,
1137 span: Span::new(start, end),
1138 leading,
1139 value: original.value.clone(),
1140 });
1141 self.map.push(origin);
1142 }
1143
1144 fn flush_trailing_pragmas(&mut self) {
1148 if self.pending_pragmas.is_empty() {
1149 return;
1150 }
1151 let pending = core::mem::take(&mut self.pending_pragmas);
1152 let mut on_last = Vec::new();
1153 let mut on_eof = Vec::new();
1154 let mut eof_file = None;
1155 for (text, held, file) in pending {
1156 self.text.push(' ');
1157 #[allow(clippy::cast_possible_truncation)]
1158 let from = self.text.len() as u32;
1159 self.text.push_str(&text);
1160 #[allow(clippy::cast_possible_truncation)]
1161 let to = self.text.len() as u32;
1162 self.map
1163 .push_range(Span::new(from, to), Origin::literal(file, held.span));
1164 let moved = Trivia {
1165 kind: held.kind,
1166 span: Span::new(from, to),
1167 };
1168 if held.kind == TriviaKind::LineComment {
1177 on_eof.push(moved);
1178 eof_file = eof_file.or(Some(file));
1179 } else {
1180 on_last.push(moved);
1181 }
1182 }
1183 if let Some(last) = self.tokens.last_mut() {
1184 last.leading.extend(on_last);
1185 }
1186 let (Some(file), false) = (eof_file, on_eof.is_empty()) else {
1187 return;
1188 };
1189 #[allow(clippy::cast_possible_truncation)]
1190 let at = self.text.len() as u32;
1191 self.tokens.push(Token {
1192 kind: zerodds_idl_cst::TokenKind::Eof,
1193 span: Span::new(at, at),
1194 leading: on_eof,
1195 value: zerodds_idl_cst::TokenValue::None,
1196 });
1197 self.map.push(Origin {
1198 file,
1199 span: Span::new(at, at),
1200 macro_chain: Vec::new(),
1201 });
1202 }
1203
1204 fn define(&mut self, rest: &str, span: Span) {
1208 let text = rest.trim_start();
1209 let name_end = text
1210 .find(|c: char| !(c.is_ascii_alphanumeric() || c == '_' || !c.is_ascii()))
1211 .unwrap_or(text.len());
1212 let name = &text[..name_end];
1213 if name.is_empty() {
1214 self.report(Diagnostic::error(
1215 DiagnosticCode::MissingToken,
1216 span,
1217 "`#define` without a name".to_string(),
1218 "OMG IDL 4.2 §7.3",
1219 ));
1220 return;
1221 }
1222 let after = &text[name_end..];
1223 let defined = if let Some(list) = after.strip_prefix('(') {
1224 let Some(close) = list.find(')') else {
1225 self.preprocessing_error(
1226 span,
1227 format!("the parameter list of `{name}` is not closed"),
1228 "ISO/IEC 14882:2003 §16.3",
1229 );
1230 return;
1231 };
1232 let params: Vec<String> = if list[..close].trim().is_empty() {
1233 Vec::new()
1234 } else {
1235 list[..close]
1236 .split(',')
1237 .map(|param| param.trim().to_string())
1238 .collect()
1239 };
1240 self.macros
1241 .define_function(name, params, &list[close + 1..])
1242 } else {
1243 self.macros.define(name, after)
1244 };
1245 if let Err(why) = defined {
1246 self.preprocessing_error(
1247 span,
1248 format!("`#define {name}`: {why}"),
1249 "ISO/IEC 14882:2003 §16.3",
1250 );
1251 }
1252 }
1253
1254 #[allow(clippy::too_many_lines)]
1255 fn directive(&mut self, raw: &str, span: Span, cursor: &mut FileCursor<'_>) {
1256 let line = splice(raw);
1260 let body = line.trim_start().trim_start_matches('#').trim();
1261 let (word, rest) = split_word(body);
1262 let keeping = cursor.keeping();
1263 let file = cursor.file;
1264
1265 match word {
1266 "define" if keeping => self.define(rest, span),
1267 "error" if keeping => {
1281 let text = rest.trim();
1282 let message = if text.is_empty() {
1283 String::from("`#error`")
1284 } else {
1285 format!("`#error {text}`")
1286 };
1287 self.report(Diagnostic::error(
1288 DiagnosticCode::ErrorDirective,
1289 span,
1290 message,
1291 "OMG IDL 4.2 §7.3, ISO/IEC 14882:2003 §16.5",
1292 ));
1293 }
1294 "undef" if keeping => {
1295 let (name, _) = split_word(rest);
1296 if macro_::RESERVED.contains(&name) {
1297 self.preprocessing_error(
1298 span,
1299 format!("`{name}` cannot be undefined"),
1300 "ISO/IEC 14882:2003 §16.8/4",
1301 );
1302 } else {
1303 self.macros.undefine(name);
1304 }
1305 }
1306 "include" if keeping => {
1307 let Some(target) = include_target(rest) else {
1308 self.report(Diagnostic::error(
1309 DiagnosticCode::UnexpectedToken,
1310 span,
1311 "`#include` expects \"path\" or <path>".to_string(),
1312 "OMG IDL 4.2 §7.3",
1313 ));
1314 return;
1315 };
1316 let from = self.map.path(file).map(ToOwned::to_owned);
1317 match self.loader.load(&target, from.as_deref()) {
1318 Some((resolved, text)) => self.include(&resolved, &text, cursor.depth + 1),
1319 None => self.report(Diagnostic::error(
1320 DiagnosticCode::UnexpectedToken,
1321 span,
1322 format!("cannot find `{target}`"),
1323 "OMG IDL 4.2 §7.3",
1324 )),
1325 }
1326 }
1327 "line" if keeping => self.line_control(rest, span, cursor),
1328 "ifdef" => {
1329 let (name, _) = split_word(rest);
1330 cursor
1331 .conditions
1332 .push(Condition::new(keeping && self.macros.is_defined(name)));
1333 }
1334 "ifndef" => {
1335 let (name, _) = split_word(rest);
1336 cursor
1337 .conditions
1338 .push(Condition::new(keeping && !self.macros.is_defined(name)));
1339 }
1340 "if" => {
1341 let taken = keeping && self.evaluate_condition(rest, span, file);
1345 cursor.conditions.push(Condition::new(taken));
1346 }
1347 "elif" => {
1348 let outer = cursor.conditions.len().saturating_sub(1);
1349 let enclosing_keeps = cursor.conditions[..outer].iter().all(Condition::keeping);
1350 let open = cursor
1351 .conditions
1352 .last()
1353 .map(|condition| condition.satisfied);
1354 match open {
1355 Some(satisfied) => {
1356 let taken = enclosing_keeps
1357 && !satisfied
1358 && self.evaluate_condition(rest, span, file);
1359 if let Some(condition) = cursor.conditions.last_mut() {
1360 condition.elif(taken);
1361 }
1362 }
1363 None => self.unmatched(span, "#elif"),
1364 }
1365 }
1366 "else" => match cursor.conditions.last_mut() {
1367 Some(condition) => condition.otherwise(),
1368 None => self.unmatched(span, "#else"),
1369 },
1370 "endif" => {
1371 if cursor.conditions.pop().is_none() {
1372 self.unmatched(span, "#endif");
1373 }
1374 }
1375 "pragma" if keeping && rest.trim() == "once" => {
1388 if let Some(current) = self.open.last().cloned()
1389 && !self.once.contains(¤t)
1390 {
1391 self.once.push(current);
1392 }
1393 }
1394 "pragma" => {
1395 if keeping {
1396 self.pending_pragmas.push((
1397 line.trim().to_string(),
1398 Trivia {
1399 kind: TriviaKind::Directive,
1400 span,
1401 },
1402 file,
1403 ));
1404 }
1405 }
1406 _ => {}
1407 }
1408 }
1409
1410 fn line_control(&mut self, rest: &str, span: Span, cursor: &FileCursor<'_>) {
1415 let tokens = self.expand_line(rest, span, cursor.file);
1416 let mut texts = tokens.iter().map(|token| token.text.as_str());
1417 let number = texts.next().and_then(|digits| {
1418 if digits.bytes().all(|b| b.is_ascii_digit()) {
1419 digits.parse::<i64>().ok()
1420 } else {
1421 None
1422 }
1423 });
1424 let name = match texts.next() {
1425 None => None,
1426 Some(literal) => match unquote(literal) {
1427 Some(name) => Some(name),
1428 None => {
1429 self.preprocessing_error(
1430 span,
1431 format!("`#line` names its file with a string literal, not `{literal}`"),
1432 "ISO/IEC 14882:2003 §16.4/4",
1433 );
1434 return;
1435 }
1436 },
1437 };
1438 let Some(number) = number.filter(|n| (1..=2_147_483_647).contains(n)) else {
1439 self.preprocessing_error(
1440 span,
1441 "`#line` takes a line number from 1 to 2147483647".to_string(),
1442 "ISO/IEC 14882:2003 §16.4/3",
1443 );
1444 return;
1445 };
1446 let next = line_of(cursor.source, span.end) + 1;
1449 let delta = number - i64::from(next);
1450 let name = name.or_else(|| {
1451 self.presumed
1452 .iter()
1453 .rev()
1454 .find(|(of, _, _)| *of == cursor.file)
1455 .and_then(|(_, _, name)| name.clone())
1456 });
1457 self.presumed.push((cursor.file, delta, name));
1458 }
1459
1460 fn expand_line(&mut self, rest: &str, span: Span, file: FileId) -> Vec<PpToken> {
1463 match pptoken::tokenize(rest) {
1464 Ok(pieces) => self.expand_isolated(
1465 pieces
1466 .into_iter()
1467 .map(|piece| {
1468 PpToken::made(piece.text, piece.space_before, Origin::literal(file, span))
1469 })
1470 .collect(),
1471 ),
1472 Err(why) => {
1473 self.preprocessing_error(span, why, "ISO/IEC 14882:2003 §2.4");
1474 Vec::new()
1475 }
1476 }
1477 }
1478
1479 fn evaluate_condition(&mut self, rest: &str, span: Span, file: FileId) -> bool {
1481 let pieces = match pptoken::tokenize(rest) {
1482 Ok(pieces) => pieces,
1483 Err(why) => {
1484 self.preprocessing_error(span, why, "ISO/IEC 14882:2003 §2.4");
1485 return false;
1486 }
1487 };
1488 let mut tokens = Vec::new();
1492 let mut index = 0;
1493 while index < pieces.len() {
1494 let piece = &pieces[index];
1495 if piece.text != "defined" {
1496 tokens.push(PpToken::made(
1497 piece.text.clone(),
1498 piece.space_before,
1499 Origin::literal(file, span),
1500 ));
1501 index += 1;
1502 continue;
1503 }
1504 let (name, next) = if pieces.get(index + 1).is_some_and(|p| p.text == "(") {
1505 let closed = pieces.get(index + 3).is_some_and(|p| p.text == ")");
1506 (pieces.get(index + 2).filter(|_| closed), index + 4)
1507 } else {
1508 (pieces.get(index + 1), index + 2)
1509 };
1510 let Some(name) = name.filter(|name| pptoken::is_identifier(&name.text)) else {
1511 self.preprocessing_error(
1512 span,
1513 "`defined` is followed by a macro name, alone or in parentheses".to_string(),
1514 "ISO/IEC 14882:2003 §16.1/1",
1515 );
1516 return false;
1517 };
1518 let answer = if self.macros.is_defined(&name.text)
1519 || macro_::RESERVED[1..].contains(&name.text.as_str())
1520 {
1521 "1"
1522 } else {
1523 "0"
1524 };
1525 tokens.push(PpToken::made(
1526 answer.to_string(),
1527 piece.space_before,
1528 Origin::literal(file, span),
1529 ));
1530 index = next;
1531 }
1532 let expanded = self.expand_isolated(tokens);
1533 if expanded.iter().any(|token| token.text == "defined") {
1534 self.preprocessing_error(
1537 span,
1538 "a macro in this condition expands to `defined`".to_string(),
1539 "ISO/IEC 14882:2003 §16.1/4",
1540 );
1541 return false;
1542 }
1543 let texts: Vec<String> = expanded.into_iter().map(|token| token.text).collect();
1544 match condition::evaluate(&texts) {
1545 Ok(outcome) => {
1546 for name in outcome.zeroed {
1547 self.report(Diagnostic::warning(
1548 DiagnosticCode::Preprocessing,
1549 span,
1550 format!("`{name}` in this condition names no macro and is taken as 0"),
1551 "ISO/IEC 14882:2003 §16.1/4",
1552 ));
1553 }
1554 outcome.value
1555 }
1556 Err(why) => {
1557 self.preprocessing_error(
1558 span,
1559 format!("`#if {}`: {why}", rest.trim()),
1560 "ISO/IEC 14882:2003 §16.1",
1561 );
1562 false
1563 }
1564 }
1565 }
1566
1567 fn unmatched(&mut self, span: Span, what: &str) {
1568 self.report(Diagnostic::error(
1569 DiagnosticCode::UnexpectedToken,
1570 span,
1571 format!("`{what}` without a matching `#if`"),
1572 "OMG IDL 4.2 §7.3",
1573 ));
1574 }
1575}
1576
1577fn splice(line: &str) -> String {
1580 line.replace("\\\r\n", "").replace("\\\n", "")
1581}
1582
1583fn line_of(source: &str, at: u32) -> u32 {
1585 let end = usize::try_from(at).unwrap_or(usize::MAX).min(source.len());
1586 let newlines = source.as_bytes()[..end]
1587 .iter()
1588 .filter(|byte| **byte == b'\n')
1589 .count();
1590 u32::try_from(newlines)
1591 .unwrap_or(u32::MAX)
1592 .saturating_add(1)
1593}
1594
1595fn quote(text: &str) -> String {
1597 let mut out = String::with_capacity(text.len() + 2);
1598 out.push('"');
1599 for ch in text.chars() {
1600 if ch == '\\' || ch == '"' {
1601 out.push('\\');
1602 }
1603 out.push(ch);
1604 }
1605 out.push('"');
1606 out
1607}
1608
1609fn unquote(literal: &str) -> Option<String> {
1612 let inner = literal.strip_prefix('"')?.strip_suffix('"')?;
1613 let mut out = String::with_capacity(inner.len());
1614 let mut chars = inner.chars();
1615 while let Some(ch) = chars.next() {
1616 if ch == '\\' {
1617 out.push(chars.next()?);
1618 } else {
1619 out.push(ch);
1620 }
1621 }
1622 Some(out)
1623}
1624
1625fn stringize(argument: &[PpToken]) -> String {
1630 let mut spelled = String::new();
1631 for (index, token) in argument.iter().enumerate() {
1632 if index > 0 && token.space_before {
1633 spelled.push(' ');
1634 }
1635 let literal = token.text.starts_with(['"', '\''])
1636 || token.text.starts_with("L\"")
1637 || token.text.starts_with("L'");
1638 if literal {
1639 for ch in token.text.chars() {
1640 if ch == '\\' || ch == '"' {
1641 spelled.push('\\');
1642 }
1643 spelled.push(ch);
1644 }
1645 } else {
1646 spelled.push_str(&token.text);
1647 }
1648 }
1649 format!("\"{spelled}\"")
1650}
1651
1652#[derive(Debug, Clone, Copy)]
1654struct Condition {
1655 active: bool,
1657 satisfied: bool,
1660}
1661
1662impl Condition {
1663 const fn new(taken: bool) -> Self {
1664 Self {
1665 active: taken,
1666 satisfied: taken,
1667 }
1668 }
1669
1670 const fn keeping(&self) -> bool {
1671 self.active
1672 }
1673
1674 const fn elif(&mut self, taken: bool) {
1675 self.active = taken && !self.satisfied;
1676 if self.active {
1677 self.satisfied = true;
1678 }
1679 }
1680
1681 const fn otherwise(&mut self) {
1682 self.active = !self.satisfied;
1683 self.satisfied = true;
1684 }
1685}
1686
1687fn split_word(text: &str) -> (&str, &str) {
1689 let trimmed = text.trim_start();
1690 match trimmed.find(char::is_whitespace) {
1691 Some(at) => (&trimmed[..at], &trimmed[at..]),
1692 None => (trimmed, ""),
1693 }
1694}
1695
1696fn include_target(rest: &str) -> Option<String> {
1698 let text = rest.trim();
1699 let inner = text
1700 .strip_prefix('"')
1701 .and_then(|s| s.strip_suffix('"'))
1702 .or_else(|| text.strip_prefix('<').and_then(|s| s.strip_suffix('>')))?;
1703 Some(inner.to_owned())
1704}