1use std::collections::HashMap;
11
12use rucc_base::{Interner, Symbol};
13use rucc_diag::{Diagnostic, Span};
14use rucc_lex::{PpToken, PpTokenKind, Punct, TokenFlags};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Builtin {
26 File,
28 FileName,
30 BaseFile,
32 Line,
34 IncludeLevel,
36 Counter,
38}
39
40impl Builtin {
41 pub const ALL: [(&'static str, Builtin); 6] = [
46 ("__FILE__", Builtin::File),
47 ("__FILE_NAME__", Builtin::FileName),
48 ("__BASE_FILE__", Builtin::BaseFile),
49 ("__LINE__", Builtin::Line),
50 ("__INCLUDE_LEVEL__", Builtin::IncludeLevel),
51 ("__COUNTER__", Builtin::Counter),
52 ];
53}
54
55#[derive(Debug, Clone)]
57pub struct MacroDef {
58 pub name: Symbol,
60 pub function_like: bool,
63 pub params: Vec<Symbol>,
65 pub variadic: Option<Symbol>,
68 pub body: Vec<PpToken>,
70 pub span: Span,
72 pub builtin: Option<Builtin>,
74}
75
76impl MacroDef {
77 #[inline]
79 pub fn is_variadic(&self) -> bool {
80 self.variadic.is_some()
81 }
82
83 #[inline]
85 pub fn arity(&self) -> usize {
86 self.params.len()
87 }
88
89 pub fn param_index(&self, name: Symbol) -> Option<usize> {
92 if let Some(at) = self.params.iter().position(|&p| p == name) {
93 return Some(at);
94 }
95 if self.variadic == Some(name) { Some(self.params.len()) } else { None }
96 }
97
98 #[inline]
100 pub fn is_variadic_param(&self, name: Symbol) -> bool {
101 self.variadic == Some(name)
102 }
103
104 pub fn same_definition_as(&self, other: &MacroDef) -> bool {
110 if self.function_like != other.function_like
111 || self.params != other.params
112 || self.variadic != other.variadic
113 || self.builtin != other.builtin
114 || self.body.len() != other.body.len()
115 {
116 return false;
117 }
118 self.body.iter().zip(&other.body).enumerate().all(|(at, (a, b))| {
119 a.kind == b.kind
120 && a.value == b.value
121 && (at == 0
124 || a.flags.has(TokenFlags::LEADING_SPACE)
125 == b.flags.has(TokenFlags::LEADING_SPACE))
126 })
127 }
128}
129
130#[derive(Debug, Default)]
132pub struct MacroTable {
133 by_name: HashMap<Symbol, MacroDef>,
134}
135
136impl MacroTable {
137 pub fn new() -> MacroTable {
139 MacroTable::default()
140 }
141
142 #[inline]
144 pub fn lookup(&self, name: Symbol) -> Option<&MacroDef> {
145 self.by_name.get(&name)
146 }
147
148 #[inline]
150 pub fn is_defined(&self, name: Symbol) -> bool {
151 self.by_name.contains_key(&name)
152 }
153
154 pub fn len(&self) -> usize {
156 self.by_name.len()
157 }
158
159 pub fn is_empty(&self) -> bool {
161 self.by_name.is_empty()
162 }
163
164 pub fn define(&mut self, def: MacroDef, interner: &Interner) -> Option<Diagnostic> {
171 let complaint =
172 self.by_name.get(&def.name).filter(|old| !old.same_definition_as(&def)).map(|old| {
173 Diagnostic::warning(format!("`{}` redefined", interner.resolve(def.name)), def.span)
174 .with_code("W0301")
175 .note("previous definition was here", old.span)
176 });
177 self.by_name.insert(def.name, def);
178 complaint
179 }
180
181 pub fn define_builtin(&mut self, name: Symbol, builtin: Builtin, span: Span) {
186 let def = MacroDef {
187 name,
188 function_like: false,
189 params: Vec::new(),
190 variadic: None,
191 body: Vec::new(),
192 span,
193 builtin: Some(builtin),
194 };
195 self.by_name.insert(name, def);
196 }
197
198 pub fn undef(&mut self, name: Symbol) -> Option<MacroDef> {
200 self.by_name.remove(&name)
201 }
202
203 pub fn sorted(&self) -> Vec<&MacroDef> {
208 let mut all: Vec<&MacroDef> = self.by_name.values().collect();
209 all.sort_by_key(|m| m.name);
210 all
211 }
212}
213
214pub fn parse_define(
225 tokens: &[PpToken],
226 interner: &mut Interner,
227) -> (Option<MacroDef>, Vec<Diagnostic>) {
228 let mut diagnostics = Vec::new();
229 let Some(&first) = tokens.first() else {
230 return (None, vec![Diagnostic::error("no macro name given in `#define`", Span::DUMMY)]);
231 };
232 if first.kind != PpTokenKind::Ident {
233 diagnostics.push(
234 Diagnostic::error("macro name must be an identifier", first.span).with_code("E0300"),
235 );
236 return (None, diagnostics);
237 }
238 let name = first.value.expect("the lexer interns every identifier");
239 let span = first.span;
240 let rest = &tokens[1..];
241
242 let opens_params = rest.first().is_some_and(|t| {
246 t.punct() == Some(Punct::LParen) && !t.flags.has(TokenFlags::LEADING_SPACE)
247 });
248
249 let (function_like, params, variadic, body) = if opens_params {
250 match parse_params(&rest[1..], interner, &mut diagnostics) {
251 Some((params, variadic, consumed)) => (true, params, variadic, &rest[1 + consumed..]),
252 None => return (None, diagnostics),
253 }
254 } else {
255 (false, Vec::new(), None, rest)
256 };
257
258 let def = MacroDef {
259 name,
260 function_like,
261 params,
262 variadic,
263 body: body.to_vec(),
264 span,
265 builtin: None,
266 };
267 check_body(&def, interner, &mut diagnostics);
268 (Some(def), diagnostics)
269}
270
271fn parse_params(
276 tokens: &[PpToken],
277 interner: &mut Interner,
278 diagnostics: &mut Vec<Diagnostic>,
279) -> Option<(Vec<Symbol>, Option<Symbol>, usize)> {
280 let va_args = interner.intern("__VA_ARGS__");
281 let mut params: Vec<Symbol> = Vec::new();
282 let mut variadic = None;
283 let mut at = 0;
284
285 if tokens.first().is_some_and(|t| t.punct() == Some(Punct::RParen)) {
286 return Some((params, None, 1));
287 }
288
289 loop {
290 let Some(&tok) = tokens.get(at) else {
291 diagnostics.push(
292 Diagnostic::error("missing `)` in macro parameter list", last_span(tokens))
293 .with_code("E0301"),
294 );
295 return None;
296 };
297 at += 1;
298
299 if tok.punct() == Some(Punct::Ellipsis) {
300 variadic = Some(va_args);
301 } else if tok.kind == PpTokenKind::Ident {
302 let sym = tok.value.expect("the lexer interns every identifier");
303 if tokens.get(at).is_some_and(|t| t.punct() == Some(Punct::Ellipsis)) {
305 at += 1;
306 variadic = Some(sym);
307 } else if sym == va_args {
308 diagnostics.push(
309 Diagnostic::error("`__VA_ARGS__` cannot be used as a parameter name", tok.span)
310 .with_code("E0302"),
311 );
312 return None;
313 } else if params.contains(&sym) {
314 diagnostics.push(
315 Diagnostic::error(
316 format!("duplicate macro parameter `{}`", interner.resolve(sym)),
317 tok.span,
318 )
319 .with_code("E0303"),
320 );
321 return None;
322 } else {
323 params.push(sym);
324 }
325 } else {
326 diagnostics.push(
327 Diagnostic::error("macro parameter must be an identifier", tok.span)
328 .with_code("E0301"),
329 );
330 return None;
331 }
332
333 match tokens.get(at).and_then(|t| t.punct()) {
334 Some(Punct::RParen) => return Some((params, variadic, at + 1)),
335 Some(Punct::Comma) if variadic.is_none() => at += 1,
336 Some(Punct::Comma) => {
337 diagnostics.push(
338 Diagnostic::error("`...` must be the last macro parameter", tokens[at].span)
339 .with_code("E0301"),
340 );
341 return None;
342 }
343 _ => {
344 diagnostics.push(
345 Diagnostic::error("missing `)` in macro parameter list", last_span(tokens))
346 .with_code("E0301"),
347 );
348 return None;
349 }
350 }
351 }
352}
353
354fn check_body(def: &MacroDef, interner: &mut Interner, diagnostics: &mut Vec<Diagnostic>) {
356 let va_opt = interner.intern("__VA_OPT__");
357 let va_args = interner.intern("__VA_ARGS__");
358
359 if let Some(first) = def.body.first().filter(|t| t.punct() == Some(Punct::HashHash)) {
360 diagnostics.push(
361 Diagnostic::error("`##` cannot appear at the start of a replacement list", first.span)
362 .with_code("E0304"),
363 );
364 }
365 let trailing =
367 def.body.last().filter(|t| def.body.len() > 1 && t.punct() == Some(Punct::HashHash));
368 if let Some(last) = trailing {
369 diagnostics.push(
370 Diagnostic::error("`##` cannot appear at the end of a replacement list", last.span)
371 .with_code("E0304"),
372 );
373 }
374
375 for (at, tok) in def.body.iter().enumerate() {
376 if def.function_like && tok.punct() == Some(Punct::Hash) {
379 let operand = def.body.get(at + 1);
380 let names_param = operand.is_some_and(|t| {
381 t.value.is_some_and(|v| def.param_index(v).is_some())
382 || (def.is_variadic() && t.value == Some(va_opt))
383 });
384 if !names_param {
385 diagnostics.push(
386 Diagnostic::error("`#` must be followed by a macro parameter", tok.span)
387 .with_code("E0305"),
388 );
389 }
390 }
391
392 if tok.kind != PpTokenKind::Ident {
393 continue;
394 }
395 if tok.value == Some(va_args) && !def.is_variadic() {
396 diagnostics.push(
397 Diagnostic::error("`__VA_ARGS__` can only appear in a variadic macro", tok.span)
398 .with_code("E0306"),
399 );
400 }
401 if tok.value == Some(va_opt) {
402 if !def.is_variadic() {
403 diagnostics.push(
404 Diagnostic::error("`__VA_OPT__` can only appear in a variadic macro", tok.span)
405 .with_code("E0306"),
406 );
407 } else if !def.body.get(at + 1).is_some_and(|t| t.punct() == Some(Punct::LParen)) {
408 diagnostics.push(
409 Diagnostic::error("`__VA_OPT__` must be followed by `(`", tok.span)
410 .with_code("E0307"),
411 );
412 }
413 }
414 }
415}
416
417fn last_span(tokens: &[PpToken]) -> Span {
419 tokens.last().map_or(Span::DUMMY, |t| t.span)
420}
421
422#[cfg(test)]
423mod tests {
424 use rucc_diag::Severity;
425 use rucc_lex::{Options, tokenize};
426
427 use super::*;
428
429 fn define(src: &str, interner: &mut Interner) -> (Option<MacroDef>, Vec<Diagnostic>) {
430 let (tokens, lex_errors) = tokenize(src.as_bytes(), 0, Options::new(), interner);
431 assert!(lex_errors.is_empty(), "the test input should lex cleanly");
432 let body: Vec<PpToken> =
433 tokens.into_iter().filter(|t| t.kind != PpTokenKind::Eof).collect();
434 parse_define(&body, interner)
435 }
436
437 #[test]
438 fn an_object_like_macro_has_no_parameter_list() {
439 let mut i = Interner::new();
440 let (def, errors) = define("PI 3.14", &mut i);
441 let def = def.expect("should parse");
442 assert!(errors.is_empty());
443 assert!(!def.function_like);
444 assert_eq!(def.body.len(), 1);
445 }
446
447 #[test]
448 fn a_space_before_the_parenthesis_makes_it_object_like() {
449 let mut i = Interner::new();
450 let (def, _) = define("A (x)", &mut i);
451 let def = def.expect("should parse");
452 assert!(!def.function_like, "`#define A (x)` defines A as the token sequence `(x)`");
453 assert_eq!(def.body.len(), 3);
454 }
455
456 #[test]
457 fn a_function_like_macro_with_no_parameters_is_not_object_like() {
458 let mut i = Interner::new();
459 let (def, _) = define("A() 1", &mut i);
460 let def = def.expect("should parse");
461 assert!(def.function_like);
462 assert_eq!(def.arity(), 0);
463 }
464
465 #[test]
466 fn the_standard_ellipsis_names_the_variadic_va_args() {
467 let mut i = Interner::new();
468 let (def, errors) = define("F(a, ...) a", &mut i);
469 let def = def.expect("should parse");
470 assert!(errors.is_empty());
471 assert_eq!(def.arity(), 1);
472 assert_eq!(def.variadic, Some(i.intern("__VA_ARGS__")));
473 }
474
475 #[test]
476 fn the_gnu_form_names_the_variadic_itself() {
477 let mut i = Interner::new();
478 let (def, errors) = define("F(a, rest...) a", &mut i);
479 let def = def.expect("should parse");
480 assert!(errors.is_empty());
481 assert_eq!(def.variadic, Some(i.intern("rest")));
482 assert_eq!(def.param_index(i.intern("rest")), Some(1));
483 }
484
485 #[test]
486 fn a_duplicate_parameter_is_rejected() {
487 let mut i = Interner::new();
488 let (def, errors) = define("F(a, a) a", &mut i);
489 assert!(def.is_none());
490 assert_eq!(errors[0].code, Some("E0303"));
491 }
492
493 #[test]
494 fn paste_cannot_start_or_end_a_replacement_list() {
495 let mut i = Interner::new();
496 let (_, start) = define("A ## b", &mut i);
497 assert_eq!(start[0].code, Some("E0304"));
498 let (_, end) = define("A b ##", &mut i);
499 assert_eq!(end[0].code, Some("E0304"));
500 }
501
502 #[test]
503 fn stringify_must_name_a_parameter_but_only_in_a_function_like_macro() {
504 let mut i = Interner::new();
505 let (_, bad) = define("F(a) # b", &mut i);
506 assert_eq!(bad[0].code, Some("E0305"));
507 let (_, fine) = define("HASH #", &mut i);
508 assert!(fine.is_empty(), "a bare `#` in an object-like macro is just a token");
509 }
510
511 #[test]
512 fn va_args_outside_a_variadic_macro_is_rejected() {
513 let mut i = Interner::new();
514 let (_, errors) = define("F(a) __VA_ARGS__", &mut i);
515 assert_eq!(errors[0].code, Some("E0306"));
516 }
517
518 #[test]
519 fn va_opt_must_be_called() {
520 let mut i = Interner::new();
521 let (_, errors) = define("F(...) __VA_OPT__", &mut i);
522 assert_eq!(errors[0].code, Some("E0307"));
523 }
524
525 #[test]
526 fn redefining_a_macro_to_the_same_thing_is_silent() {
527 let mut i = Interner::new();
528 let mut table = MacroTable::new();
529 let (first, _) = define("A 1 + 2", &mut i);
530 let (again, _) = define("A 1 + 2", &mut i);
531 assert!(table.define(first.expect("should parse"), &i).is_none());
532 assert!(table.define(again.expect("should parse"), &i).is_none());
533 assert_eq!(table.len(), 1);
534 }
535
536 #[test]
537 fn redefining_a_macro_differently_warns_and_takes_the_new_one() {
538 let mut i = Interner::new();
539 let mut table = MacroTable::new();
540 let (first, _) = define("A 1", &mut i);
541 let (again, _) = define("A 2", &mut i);
542 table.define(first.expect("should parse"), &i);
543 let warning = table.define(again.expect("should parse"), &i).expect("should warn");
544 assert_eq!(warning.severity, Severity::Warning);
545 assert_eq!(warning.code, Some("W0301"));
546 assert_eq!(table.lookup(i.intern("A")).expect("still defined").body.len(), 1);
547 }
548
549 #[test]
550 fn whitespace_inside_the_replacement_list_is_part_of_the_definition() {
551 let mut i = Interner::new();
552 let (a, _) = define("A x+y", &mut i);
553 let (b, _) = define("A x + y", &mut i);
554 assert!(
555 !a.expect("should parse").same_definition_as(&b.expect("should parse")),
556 "the standard compares spelling including whitespace separation"
557 );
558 }
559
560 #[test]
561 fn a_builtin_macro_is_defined_like_any_other() {
562 let mut i = Interner::new();
563 let mut table = MacroTable::new();
564 let name = i.intern("__LINE__");
565 table.define_builtin(name, Builtin::Line, Span::new(0, 0));
566 assert!(table.is_defined(name), "`#ifdef __LINE__` is true");
567 assert_eq!(table.lookup(name).and_then(|d| d.builtin), Some(Builtin::Line));
568 assert!(table.undef(name).is_some(), "`#undef __LINE__` is allowed, as it is in GCC");
569 }
570
571 #[test]
572 fn redefining_a_builtin_is_a_redefinition() {
573 let mut i = Interner::new();
574 let mut table = MacroTable::new();
575 let name = i.intern("__FILE__");
576 table.define_builtin(name, Builtin::File, Span::new(0, 0));
577 let (def, _) = define("__FILE__", &mut i);
580 let warning = table.define(def.expect("should parse"), &i).expect("should warn");
581 assert_eq!(warning.code, Some("W0301"));
582 assert!(table.lookup(name).expect("still defined").builtin.is_none());
583 }
584
585 #[test]
586 fn undefining_something_that_was_never_defined_is_fine() {
587 let mut i = Interner::new();
588 let mut table = MacroTable::new();
589 assert!(table.undef(i.intern("nothing")).is_none());
590 }
591}