use super::pptoken::{Piece, is_identifier, tokenize};
use alloc::borrow::ToOwned;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use zerodds_idl_cst::Span;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MacroUse {
pub name: String,
pub at: Span,
pub file: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Macro {
pub params: Option<Vec<String>>,
pub(crate) body: Vec<Piece>,
}
pub const RESERVED: [&str; 3] = ["defined", "__LINE__", "__FILE__"];
#[derive(Debug, Clone, Default)]
pub struct MacroTable {
entries: Vec<(String, Macro)>,
}
impl MacroTable {
#[must_use]
pub const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn define(&mut self, name: &str, body: &str) -> Result<(), String> {
self.insert(name, None, body)
}
pub fn define_function(
&mut self,
name: &str,
params: Vec<String>,
body: &str,
) -> Result<(), String> {
for (index, param) in params.iter().enumerate() {
if !is_identifier(param) {
return Err(format!("`{param}` is not a parameter name"));
}
if params[..index].contains(param) {
return Err(format!("parameter `{param}` is written twice"));
}
}
self.insert(name, Some(params), body)
}
fn insert(
&mut self,
name: &str,
params: Option<Vec<String>>,
body: &str,
) -> Result<(), String> {
if RESERVED.contains(&name) {
return Err(format!("`{name}` cannot be defined"));
}
if !is_identifier(name) {
return Err(format!("`{name}` is not a macro name"));
}
let body = tokenize(body)?;
if body.first().is_some_and(|p| p.text == "##")
|| body.last().is_some_and(|p| p.text == "##")
{
return Err("`##` at either end of a replacement list has nothing to paste".to_owned());
}
if let Some(params) = ¶ms {
for (index, piece) in body.iter().enumerate() {
if piece.text == "#"
&& !body
.get(index + 1)
.is_some_and(|next| params.contains(&next.text))
{
return Err(
"`#` in a function-like macro is not followed by a parameter".to_owned(),
);
}
}
}
self.undefine(name);
self.entries.push((name.to_owned(), Macro { params, body }));
Ok(())
}
pub fn undefine(&mut self, name: &str) {
self.entries.retain(|(defined, _)| defined != name);
}
#[must_use]
pub fn is_defined(&self, name: &str) -> bool {
self.entries.iter().any(|(defined, _)| defined == name)
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&Macro> {
self.entries
.iter()
.find(|(defined, _)| defined == name)
.map(|(_, found)| found)
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}