Skip to main content

zerodds_idl_parse/preprocess/
macro_.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Macro definitions and their use sites. Spec §3.4.
4//!
5//! Both kinds ISO/IEC 14882:2003 §16.3 defines: object-like, and
6//! function-like with a parameter list. The replacement list is held as
7//! preprocessing tokens, which is the form §16.3 states its rules in.
8
9use super::pptoken::{Piece, is_identifier, tokenize};
10use alloc::borrow::ToOwned;
11use alloc::format;
12use alloc::string::String;
13use alloc::vec::Vec;
14use zerodds_idl_cst::Span;
15
16/// One place a macro was used.
17///
18/// Recorded per expanded token so a diagnostic inside an expansion can
19/// name the macro as well as the text.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct MacroUse {
22    /// Which macro.
23    pub name: String,
24    /// Where it was written.
25    pub at: Span,
26    /// Which file that was, as a raw id.
27    pub file: u32,
28}
29
30/// One macro.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct Macro {
33    /// The parameters of a function-like macro; `None` for an
34    /// object-like one. `Some(vec![])` is `F()`, which is function-like
35    /// with no parameter.
36    pub params: Option<Vec<String>>,
37    /// The replacement list.
38    pub(crate) body: Vec<Piece>,
39}
40
41/// The names §16.3/8 and §16.8/4 put out of reach of `#define`:
42/// `defined`, and the predefined macros this preprocessor answers.
43pub const RESERVED: [&str; 3] = ["defined", "__LINE__", "__FILE__"];
44
45/// The macros in scope.
46#[derive(Debug, Clone, Default)]
47pub struct MacroTable {
48    entries: Vec<(String, Macro)>,
49}
50
51impl MacroTable {
52    /// An empty table.
53    #[must_use]
54    pub const fn new() -> Self {
55        Self {
56            entries: Vec::new(),
57        }
58    }
59
60    /// Define `name` as the object-like macro `body`, replacing any
61    /// previous definition.
62    ///
63    /// # Errors
64    /// A replacement list §16.3.3/1 forbids, or one that does not
65    /// tokenize, in one sentence.
66    pub fn define(&mut self, name: &str, body: &str) -> Result<(), String> {
67        self.insert(name, None, body)
68    }
69
70    /// Define `name` as a function-like macro.
71    ///
72    /// # Errors
73    /// As [`Self::define`], and a parameter list §16.3/4 forbids — a
74    /// name that is not an identifier, or one written twice — or a `#`
75    /// not followed by a parameter (§16.3.2/1).
76    pub fn define_function(
77        &mut self,
78        name: &str,
79        params: Vec<String>,
80        body: &str,
81    ) -> Result<(), String> {
82        for (index, param) in params.iter().enumerate() {
83            if !is_identifier(param) {
84                return Err(format!("`{param}` is not a parameter name"));
85            }
86            if params[..index].contains(param) {
87                return Err(format!("parameter `{param}` is written twice"));
88            }
89        }
90        self.insert(name, Some(params), body)
91    }
92
93    fn insert(
94        &mut self,
95        name: &str,
96        params: Option<Vec<String>>,
97        body: &str,
98    ) -> Result<(), String> {
99        if RESERVED.contains(&name) {
100            return Err(format!("`{name}` cannot be defined"));
101        }
102        if !is_identifier(name) {
103            return Err(format!("`{name}` is not a macro name"));
104        }
105        let body = tokenize(body)?;
106        // §16.3.3/1: "A ## preprocessing token shall not occur at the
107        // beginning or at the end of a replacement list".
108        if body.first().is_some_and(|p| p.text == "##")
109            || body.last().is_some_and(|p| p.text == "##")
110        {
111            return Err("`##` at either end of a replacement list has nothing to paste".to_owned());
112        }
113        // §16.3.2/1: in a function-like macro, "each # preprocessing
114        // token in the replacement list shall be followed by a parameter".
115        if let Some(params) = &params {
116            for (index, piece) in body.iter().enumerate() {
117                if piece.text == "#"
118                    && !body
119                        .get(index + 1)
120                        .is_some_and(|next| params.contains(&next.text))
121                {
122                    return Err(
123                        "`#` in a function-like macro is not followed by a parameter".to_owned(),
124                    );
125                }
126            }
127        }
128        self.undefine(name);
129        self.entries.push((name.to_owned(), Macro { params, body }));
130        Ok(())
131    }
132
133    /// Remove `name`, if it was defined.
134    pub fn undefine(&mut self, name: &str) {
135        self.entries.retain(|(defined, _)| defined != name);
136    }
137
138    /// Whether `name` is defined, whatever its body.
139    ///
140    /// An include guard defines a name with an empty body, so emptiness
141    /// is not absence.
142    #[must_use]
143    pub fn is_defined(&self, name: &str) -> bool {
144        self.entries.iter().any(|(defined, _)| defined == name)
145    }
146
147    /// The macro called `name`.
148    #[must_use]
149    pub fn get(&self, name: &str) -> Option<&Macro> {
150        self.entries
151            .iter()
152            .find(|(defined, _)| defined == name)
153            .map(|(_, found)| found)
154    }
155
156    /// How many macros are defined.
157    #[must_use]
158    pub fn len(&self) -> usize {
159        self.entries.len()
160    }
161
162    /// Whether none are.
163    #[must_use]
164    pub fn is_empty(&self) -> bool {
165        self.entries.is_empty()
166    }
167}