use std::collections::HashMap;
use crate::intern::InternedStr;
use crate::source::SourceLocation;
use crate::token::{Comment, Token, TokenKind};
#[derive(Debug, Clone, PartialEq)]
pub enum MacroKind {
Object,
Function {
params: Vec<InternedStr>,
is_variadic: bool,
},
}
#[derive(Debug, Clone)]
pub struct MacroDef {
pub name: InternedStr,
pub kind: MacroKind,
pub body: Vec<Token>,
pub def_loc: SourceLocation,
pub leading_comments: Vec<Comment>,
pub is_builtin: bool,
pub is_target: bool,
pub has_token_pasting: bool,
}
impl MacroDef {
pub fn object(
name: InternedStr,
body: Vec<Token>,
def_loc: SourceLocation,
) -> Self {
let has_token_pasting = body.iter()
.any(|t| matches!(t.kind, TokenKind::HashHash));
Self {
name,
kind: MacroKind::Object,
body,
def_loc,
leading_comments: Vec::new(),
is_builtin: false,
is_target: false,
has_token_pasting,
}
}
pub fn function(
name: InternedStr,
params: Vec<InternedStr>,
is_variadic: bool,
body: Vec<Token>,
def_loc: SourceLocation,
) -> Self {
let has_token_pasting = body.iter()
.any(|t| matches!(t.kind, TokenKind::HashHash));
Self {
name,
kind: MacroKind::Function { params, is_variadic },
body,
def_loc,
leading_comments: Vec::new(),
is_builtin: false,
is_target: false,
has_token_pasting,
}
}
pub fn with_target(mut self, is_target: bool) -> Self {
self.is_target = is_target;
self
}
pub fn with_comments(mut self, comments: Vec<Comment>) -> Self {
self.leading_comments = comments;
self
}
pub fn as_builtin(mut self) -> Self {
self.is_builtin = true;
self
}
pub fn is_function(&self) -> bool {
matches!(self.kind, MacroKind::Function { .. })
}
pub fn param_count(&self) -> usize {
match &self.kind {
MacroKind::Object => 0,
MacroKind::Function { params, .. } => params.len(),
}
}
pub fn is_variadic(&self) -> bool {
matches!(self.kind, MacroKind::Function { is_variadic: true, .. })
}
}
#[derive(Debug, Default)]
pub struct MacroTable {
macros: HashMap<InternedStr, MacroDef>,
}
impl MacroTable {
pub fn new() -> Self {
Self {
macros: HashMap::new(),
}
}
pub fn define(&mut self, def: MacroDef, interner: &crate::intern::StringInterner) -> Option<MacroDef> {
if let Some(existing) = self.macros.get(&def.name) {
if existing.is_builtin {
let name_str = interner.get(def.name);
if name_str.starts_with("__") {
return None;
}
}
}
self.macros.insert(def.name, def)
}
pub fn undefine(&mut self, name: InternedStr) -> Option<MacroDef> {
self.macros.remove(&name)
}
pub fn get(&self, name: InternedStr) -> Option<&MacroDef> {
self.macros.get(&name)
}
pub fn is_defined(&self, name: InternedStr) -> bool {
self.macros.contains_key(&name)
}
pub fn iter(&self) -> impl Iterator<Item = (&InternedStr, &MacroDef)> {
self.macros.iter()
}
pub fn iter_target_macros(&self) -> impl Iterator<Item = &MacroDef> {
self.macros.values().filter(|def| def.is_target)
}
pub fn len(&self) -> usize {
self.macros.len()
}
pub fn is_empty(&self) -> bool {
self.macros.is_empty()
}
pub fn user_defined(&self) -> impl Iterator<Item = (&InternedStr, &MacroDef)> {
self.macros.iter().filter(|(_, def)| !def.is_builtin)
}
pub fn dump_filtered(&self, filter: &str, interner: &crate::intern::StringInterner) {
let mut names: Vec<_> = self.macros.keys().collect();
names.sort_by_key(|id| interner.get(**id));
for &name in &names {
let name_str = interner.get(*name);
if !filter.is_empty() && !name_str.contains(filter) {
continue;
}
if let Some(def) = self.macros.get(name) {
let kind_str = match &def.kind {
MacroKind::Object => "object".to_string(),
MacroKind::Function { params, is_variadic } => {
let params_str: Vec<_> = params.iter()
.map(|p| interner.get(*p).to_string())
.collect();
if *is_variadic {
format!("function({}...)", params_str.join(", "))
} else {
format!("function({})", params_str.join(", "))
}
}
};
let body_str: String = def.body.iter()
.map(|t| t.kind.format(interner))
.collect::<Vec<_>>()
.join(" ");
let flags = format!(
"{}{}",
if def.is_target { "T" } else { "" },
if def.is_builtin { "B" } else { "" },
);
println!("#define {} [{}] {} = {}",
name_str,
kind_str,
if flags.is_empty() { "".to_string() } else { format!("({})", flags) },
body_str
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::intern::StringInterner;
use crate::source::FileId;
#[test]
fn test_object_macro() {
let mut interner = StringInterner::new();
let name = interner.intern("FOO");
let loc = SourceLocation::new(FileId::default(), 1, 1);
let def = MacroDef::object(name, vec![], loc);
assert!(!def.is_function());
assert_eq!(def.param_count(), 0);
assert!(!def.is_variadic());
}
#[test]
fn test_function_macro() {
let mut interner = StringInterner::new();
let name = interner.intern("MAX");
let a = interner.intern("a");
let b = interner.intern("b");
let loc = SourceLocation::new(FileId::default(), 1, 1);
let def = MacroDef::function(name, vec![a, b], false, vec![], loc);
assert!(def.is_function());
assert_eq!(def.param_count(), 2);
assert!(!def.is_variadic());
}
#[test]
fn test_variadic_macro() {
let mut interner = StringInterner::new();
let name = interner.intern("PRINTF");
let fmt = interner.intern("fmt");
let loc = SourceLocation::new(FileId::default(), 1, 1);
let def = MacroDef::function(name, vec![fmt], true, vec![], loc);
assert!(def.is_function());
assert!(def.is_variadic());
}
#[test]
fn test_macro_table() {
let mut interner = StringInterner::new();
let mut table = MacroTable::new();
let foo = interner.intern("FOO");
let bar = interner.intern("BAR");
let loc = SourceLocation::new(FileId::default(), 1, 1);
assert!(table.define(MacroDef::object(foo, vec![], loc.clone()), &interner).is_none());
assert!(table.define(MacroDef::object(bar, vec![], loc.clone()), &interner).is_none());
assert_eq!(table.len(), 2);
assert!(table.is_defined(foo));
assert!(table.get(foo).is_some());
let old = table.define(MacroDef::object(foo, vec![], loc), &interner);
assert!(old.is_some());
assert_eq!(table.len(), 2);
assert!(table.undefine(foo).is_some());
assert!(!table.is_defined(foo));
assert_eq!(table.len(), 1);
}
}