zerodds_idl_parse/preprocess/
macro_.rs1use 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#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct MacroUse {
22 pub name: String,
24 pub at: Span,
26 pub file: u32,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct Macro {
33 pub params: Option<Vec<String>>,
37 pub(crate) body: Vec<Piece>,
39}
40
41pub const RESERVED: [&str; 3] = ["defined", "__LINE__", "__FILE__"];
44
45#[derive(Debug, Clone, Default)]
47pub struct MacroTable {
48 entries: Vec<(String, Macro)>,
49}
50
51impl MacroTable {
52 #[must_use]
54 pub const fn new() -> Self {
55 Self {
56 entries: Vec::new(),
57 }
58 }
59
60 pub fn define(&mut self, name: &str, body: &str) -> Result<(), String> {
67 self.insert(name, None, body)
68 }
69
70 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 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 if let Some(params) = ¶ms {
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 pub fn undefine(&mut self, name: &str) {
135 self.entries.retain(|(defined, _)| defined != name);
136 }
137
138 #[must_use]
143 pub fn is_defined(&self, name: &str) -> bool {
144 self.entries.iter().any(|(defined, _)| defined == name)
145 }
146
147 #[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 #[must_use]
158 pub fn len(&self) -> usize {
159 self.entries.len()
160 }
161
162 #[must_use]
164 pub fn is_empty(&self) -> bool {
165 self.entries.is_empty()
166 }
167}