pub mod builtin;
pub mod expand;
use crate::ast::{Declaration, Expr, Span, Stmt, TypeExpr};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct MacroError {
pub message: String,
pub span: Option<Span>,
}
impl MacroError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
span: None,
}
}
pub fn with_span(message: impl Into<String>, span: Span) -> Self {
Self {
message: message.into(),
span: Some(span),
}
}
pub fn undefined(name: &str) -> Self {
Self::new(format!("undefined macro: #{}", name))
}
pub fn invalid_argument(msg: &str) -> Self {
Self::new(format!("invalid macro argument: {}", msg))
}
pub fn arity_mismatch(name: &str, expected: usize, actual: usize) -> Self {
Self::new(format!(
"macro #{} expects {} argument(s), got {}",
name, expected, actual
))
}
pub fn type_error(expected: &str, actual: &str) -> Self {
Self::new(format!(
"macro type error: expected {}, got {}",
expected, actual
))
}
}
impl fmt::Display for MacroError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(span) = &self.span {
write!(
f,
"macro error at line {}, column {}: {}",
span.line, span.column, self.message
)
} else {
write!(f, "macro error: {}", self.message)
}
}
}
impl std::error::Error for MacroError {}
#[derive(Debug, Clone, PartialEq)]
pub enum MacroInput {
Empty,
Expr(Box<Expr>),
ExprList(Vec<Expr>),
Tokens(Vec<MacroToken>),
Declaration(Box<Declaration>),
Config(HashMap<String, MacroValue>),
Ident(String),
IdentList(Vec<String>),
}
impl MacroInput {
pub fn empty() -> Self {
MacroInput::Empty
}
pub fn expr(expr: Expr) -> Self {
MacroInput::Expr(Box::new(expr))
}
pub fn expr_list(exprs: Vec<Expr>) -> Self {
MacroInput::ExprList(exprs)
}
pub fn ident(name: impl Into<String>) -> Self {
MacroInput::Ident(name.into())
}
pub fn ident_list(names: Vec<String>) -> Self {
MacroInput::IdentList(names)
}
pub fn is_empty(&self) -> bool {
matches!(self, MacroInput::Empty)
}
pub fn as_expr(&self) -> Option<&Expr> {
match self {
MacroInput::Expr(e) => Some(e),
_ => None,
}
}
pub fn as_expr_list(&self) -> Option<&[Expr]> {
match self {
MacroInput::ExprList(list) => Some(list),
MacroInput::Expr(_) => None, _ => None,
}
}
pub fn as_ident(&self) -> Option<&str> {
match self {
MacroInput::Ident(s) => Some(s),
_ => None,
}
}
pub fn as_ident_list(&self) -> Option<&[String]> {
match self {
MacroInput::IdentList(list) => Some(list),
_ => None,
}
}
pub fn arg_count(&self) -> usize {
match self {
MacroInput::Empty => 0,
MacroInput::Expr(_) => 1,
MacroInput::ExprList(list) => list.len(),
MacroInput::Tokens(tokens) => tokens.len(),
MacroInput::Declaration(_) => 1,
MacroInput::Config(map) => map.len(),
MacroInput::Ident(_) => 1,
MacroInput::IdentList(list) => list.len(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct MacroToken {
pub kind: MacroTokenKind,
pub text: String,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacroTokenKind {
Ident,
Keyword,
Literal,
Punct,
Delim,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MacroValue {
Bool(bool),
Int(i64),
String(String),
List(Vec<MacroValue>),
}
impl MacroValue {
pub fn as_bool(&self) -> Option<bool> {
match self {
MacroValue::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_string(&self) -> Option<&str> {
match self {
MacroValue::String(s) => Some(s),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum MacroOutput {
None,
Expr(Box<Expr>),
ExprList(Vec<Expr>),
Stmt(Box<Stmt>),
StmtList(Vec<Stmt>),
Declaration(Box<Declaration>),
DeclarationList(Vec<Declaration>),
Type(Box<TypeExpr>),
Tokens(Vec<MacroToken>),
}
impl MacroOutput {
pub fn none() -> Self {
MacroOutput::None
}
pub fn expr(expr: Expr) -> Self {
MacroOutput::Expr(Box::new(expr))
}
pub fn expr_list(exprs: Vec<Expr>) -> Self {
MacroOutput::ExprList(exprs)
}
pub fn stmt(stmt: Stmt) -> Self {
MacroOutput::Stmt(Box::new(stmt))
}
pub fn declaration(decl: Declaration) -> Self {
MacroOutput::Declaration(Box::new(decl))
}
pub fn is_none(&self) -> bool {
matches!(self, MacroOutput::None)
}
pub fn as_expr(&self) -> Option<&Expr> {
match self {
MacroOutput::Expr(e) => Some(e),
_ => None,
}
}
pub fn into_expr(self) -> Option<Expr> {
match self {
MacroOutput::Expr(e) => Some(*e),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct MacroContext {
pub file_path: Option<String>,
pub line: usize,
pub column: usize,
pub env_vars: HashMap<String, String>,
pub cfg_flags: HashMap<String, bool>,
pub features: Vec<String>,
}
impl MacroContext {
pub fn new() -> Self {
Self {
file_path: None,
line: 0,
column: 0,
env_vars: std::env::vars().collect(),
cfg_flags: HashMap::new(),
features: Vec::new(),
}
}
pub fn with_location(file: Option<String>, line: usize, column: usize) -> Self {
Self {
file_path: file,
line,
column,
..Self::new()
}
}
pub fn set_cfg(&mut self, key: impl Into<String>, value: bool) {
self.cfg_flags.insert(key.into(), value);
}
pub fn get_cfg(&self, key: &str) -> bool {
self.cfg_flags.get(key).copied().unwrap_or(false)
}
pub fn add_feature(&mut self, feature: impl Into<String>) {
self.features.push(feature.into());
}
pub fn has_feature(&self, feature: &str) -> bool {
self.features.iter().any(|f| f == feature)
}
pub fn get_env(&self, key: &str) -> Option<&str> {
self.env_vars.get(key).map(|s| s.as_str())
}
}
impl Default for MacroContext {
fn default() -> Self {
Self::new()
}
}
pub trait Macro: Send + Sync {
fn name(&self) -> &str;
fn expand(&self, input: MacroInput, ctx: &MacroContext) -> Result<MacroOutput, MacroError>;
fn description(&self) -> &str {
""
}
fn is_attribute_macro(&self) -> bool {
false
}
fn is_expr_macro(&self) -> bool {
true
}
fn min_args(&self) -> usize {
0
}
fn max_args(&self) -> Option<usize> {
None
}
fn validate(&self, input: &MacroInput) -> Result<(), MacroError> {
let count = input.arg_count();
let min = self.min_args();
if count < min {
return Err(MacroError::arity_mismatch(self.name(), min, count));
}
if let Some(max) = self.max_args() {
if count > max {
return Err(MacroError::arity_mismatch(self.name(), max, count));
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MacroInvocation {
pub name: String,
pub args: Vec<Expr>,
pub span: Span,
}
impl MacroInvocation {
pub fn new(name: impl Into<String>, args: Vec<Expr>, span: Span) -> Self {
Self {
name: name.into(),
args,
span,
}
}
pub fn simple(name: impl Into<String>, span: Span) -> Self {
Self::new(name, Vec::new(), span)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MacroAttribute {
pub name: String,
pub args: Vec<AttributeArg>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AttributeArg {
Ident(String),
KeyValue {
key: String,
value: Expr,
},
Nested {
name: String,
args: Vec<AttributeArg>,
},
}
impl MacroAttribute {
pub fn new(name: impl Into<String>, args: Vec<AttributeArg>, span: Span) -> Self {
Self {
name: name.into(),
args,
span,
}
}
pub fn simple(name: impl Into<String>, span: Span) -> Self {
Self::new(name, Vec::new(), span)
}
pub fn with_idents(name: impl Into<String>, idents: Vec<String>, span: Span) -> Self {
let args = idents.into_iter().map(AttributeArg::Ident).collect();
Self::new(name, args, span)
}
}
pub use builtin::BuiltinMacros;
pub use expand::MacroExpander;
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Literal;
#[test]
fn test_macro_error() {
let err = MacroError::undefined("test");
assert!(err.message.contains("undefined macro"));
let err = MacroError::arity_mismatch("test", 2, 1);
assert!(err.message.contains("expects 2 argument(s)"));
}
#[test]
fn test_macro_input() {
let input = MacroInput::empty();
assert!(input.is_empty());
assert_eq!(input.arg_count(), 0);
let input = MacroInput::expr(Expr::Literal(Literal::Int(42)));
assert!(!input.is_empty());
assert_eq!(input.arg_count(), 1);
assert!(input.as_expr().is_some());
}
#[test]
fn test_macro_output() {
let output = MacroOutput::none();
assert!(output.is_none());
let output = MacroOutput::expr(Expr::Literal(Literal::String("hello".to_string())));
assert!(!output.is_none());
assert!(output.as_expr().is_some());
}
#[test]
fn test_macro_context() {
let mut ctx = MacroContext::new();
ctx.set_cfg("debug", true);
assert!(ctx.get_cfg("debug"));
assert!(!ctx.get_cfg("release"));
ctx.add_feature("async");
assert!(ctx.has_feature("async"));
assert!(!ctx.has_feature("sync"));
}
#[test]
fn test_macro_invocation() {
let invoc = MacroInvocation::simple("stringify", Span::default());
assert_eq!(invoc.name, "stringify");
assert!(invoc.args.is_empty());
}
#[test]
fn test_macro_attribute() {
let attr = MacroAttribute::with_idents(
"derive",
vec!["Debug".to_string(), "Clone".to_string()],
Span::default(),
);
assert_eq!(attr.name, "derive");
assert_eq!(attr.args.len(), 2);
}
}