#![deny(missing_debug_implementations)]
#![warn(
missing_docs,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_lossless,
clippy::cast_possible_wrap,
clippy::clear_with_drain,
clippy::dbg_macro,
clippy::deref_by_slicing,
clippy::doc_link_with_quotes,
clippy::doc_markdown,
clippy::explicit_deref_methods,
clippy::get_unwrap,
clippy::impl_trait_in_params,
clippy::inefficient_to_string,
clippy::redundant_else,
clippy::semicolon_if_nothing_returned,
clippy::should_panic_without_expect,
clippy::string_add,
clippy::string_to_string,
clippy::used_underscore_binding,
clippy::wildcard_imports
)]
#![doc = include_str!("../doc/00-top-image.md")]
#![doc = include_str!("../doc/01-textbook-example.md")]
#![doc = include_str!("../doc/02-what-can-it-detect.md")]
#![doc = include_str!("../doc/03-opinionated.md")]
#![doc = include_str!("../doc/98-msrv.md")]
#![doc = include_str!("../doc/99-license.md")]
use std::{marker::Copy, str::FromStr};
pub use error::{Error, MacroRuleNode};
use grammar::DynamicState;
pub use grammar::TokenDescription;
#[macro_use]
mod macros;
mod error;
mod expansion;
mod grammar;
mod list;
mod matcher;
mod repetition_stack;
#[doc(hidden)]
pub mod span;
mod substitution;
pub fn check_macro<Span>(
ctx: InvocationContext,
input: Vec<TokenTree<Span>>,
) -> Result<(), Error<Span>>
where
Span: Copy + 'static,
{
let mut iter = input.into_iter();
while let Some(head) = iter.next() {
let TokenTreeKind::Parenthesed(matcher) = head.kind else {
return Err(Error::ParsingFailed {
what: vec![MacroRuleNode::Matcher],
where_: head.span,
});
};
let matcher = matcher::TokenTree::from_generic(matcher)?;
let matcher = matcher::Matcher::from_generic(&matcher)?;
let Some(token) = iter.next() else {
return Err(Error::UnexpectedEnd {
last_token: Some(head.span),
});
};
let TokenTreeKind::Terminal(Terminal::FatArrow) = token.kind else {
return Err(Error::ParsingFailed {
what: vec![MacroRuleNode::Terminal(Terminal::FatArrow)],
where_: token.span,
});
};
let Some(token) = iter.next() else {
return Err(Error::UnexpectedEnd {
last_token: Some(token.span),
});
};
let TokenTreeKind::CurlyBraced(substitution) = token.kind else {
return Err(Error::ParsingFailed {
what: vec![MacroRuleNode::Transcriber],
where_: token.span,
});
};
let substitution = substitution::TokenTree::from_generic(substitution)?;
repetition_stack::check(&matcher, &substitution)?;
expansion::check_arm(ctx.to_state(), matcher, &substitution, token.span)?;
if let Some(semi) = iter.next() {
let TokenTreeKind::Terminal(Terminal::Semicolon) = semi.kind else {
return Err(Error::ParsingFailed {
what: vec![MacroRuleNode::Terminal(Terminal::Semicolon)],
where_: semi.span,
});
};
}
}
Ok(())
}
pub(crate) trait Spannable<Span> {
type Output;
fn with_span(self, span: Span) -> Self::Output;
}
#[derive(Clone, Debug, PartialEq)]
pub struct TokenTree<Span> {
pub kind: TokenTreeKind<Span>,
pub span: Span,
}
#[doc(hidden)]
#[allow(non_snake_case, unused)]
impl<Span> TokenTree<Span> {
pub fn terminal(span: Span, t: Terminal) -> TokenTree<Span> {
TokenTree {
kind: TokenTreeKind::Terminal(t),
span,
}
}
pub fn parenthesed(span: Span, i: Vec<TokenTree<Span>>) -> TokenTree<Span> {
TokenTree {
kind: TokenTreeKind::Parenthesed(i),
span,
}
}
pub fn curly_braced(span: Span, i: Vec<TokenTree<Span>>) -> TokenTree<Span> {
TokenTree {
kind: TokenTreeKind::CurlyBraced(i),
span,
}
}
pub fn bracketed(span: Span, i: Vec<TokenTree<Span>>) -> TokenTree<Span> {
TokenTree {
kind: TokenTreeKind::Bracketed(i),
span,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TokenTreeKind<Span> {
Terminal(Terminal),
Parenthesed(Vec<TokenTree<Span>>),
CurlyBraced(Vec<TokenTree<Span>>),
Bracketed(Vec<TokenTree<Span>>),
}
impl_spannable!(TokenTreeKind<Span> => TokenTree);
#[non_exhaustive]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum Terminal {
Ident(String),
As,
Async,
Await,
Break,
Const,
Continue,
Crate,
Dyn,
Else,
Enum,
Extern,
False,
Fn,
For,
If,
Impl,
In,
Let,
Loop,
Match,
Mod,
Move,
Mut,
Pub,
Ref,
Return,
Self_,
SelfUpper,
Static,
Struct,
Super,
Trait,
True,
Type,
Union,
Unsafe,
Use,
Where,
While,
Abstract,
Become,
Box,
Do,
Final,
Macro,
Override,
Priv,
Try,
Typeof,
Unsized,
Virtual,
Yield,
Literal(String),
Plus,
Minus,
Star,
Slash,
Percent,
Caret,
Not,
And,
Or,
AndAnd,
OrOr,
Shl,
Shr,
PlusEquals,
MinusEquals,
StarEquals,
SlashEquals,
PercentEquals,
CaretEquals,
AndEquals,
OrEquals,
ShlEquals,
ShrEquals,
Equals,
EqualsEquals,
NotEquals,
GreaterThan,
LessThan,
GreaterThanEquals,
LessThanEquals,
At,
Underscore,
Dot,
DotDot,
DotDotDot,
DotDotEquals,
Comma,
Semicolon,
Colon,
ColonColon,
RightArrow,
FatArrow,
Pound,
Dollar,
QuestionMark,
}
impl_spannable!(Terminal => TokenTree);
impl<Span> From<Terminal> for TokenTreeKind<Span> {
fn from(value: Terminal) -> TokenTreeKind<Span> {
TokenTreeKind::Terminal(value)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum InvocationContext {
Expr,
Item,
Pat,
Stmt,
Ty,
}
impl InvocationContext {
fn to_state<T>(self) -> DynamicState<T>
where
T: Copy,
{
match self {
InvocationContext::Expr => DynamicState::expr(),
InvocationContext::Item => DynamicState::item(),
InvocationContext::Pat => DynamicState::pat(),
InvocationContext::Stmt => DynamicState::stmt(),
InvocationContext::Ty => DynamicState::ty(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum FragmentKind {
Block,
Expr,
Ident,
Item,
Lifetime,
Meta,
Pat,
Path,
PatParam,
Stmt,
Tt,
Ty,
Vis,
}
impl FromStr for FragmentKind {
type Err = ();
fn from_str(s: &str) -> Result<FragmentKind, ()> {
Ok(match s {
"block" => FragmentKind::Block,
"expr" => FragmentKind::Expr,
"ident" => FragmentKind::Ident,
"item" => FragmentKind::Item,
"lifetime" => FragmentKind::Lifetime,
"meta" => FragmentKind::Meta,
"pat" => FragmentKind::Pat,
"path" => FragmentKind::Path,
"pat_param" => FragmentKind::PatParam,
"stmt" => FragmentKind::Stmt,
"tt" => FragmentKind::Tt,
"ty" => FragmentKind::Ty,
"vis" => FragmentKind::Vis,
_ => return Err(()),
})
}
}
impl FromStr for InvocationContext {
type Err = ();
fn from_str(s: &str) -> Result<InvocationContext, ()> {
Ok(match s {
"item" => InvocationContext::Item,
"expr" => InvocationContext::Expr,
_ => return Err(()),
})
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct RepetitionQuantifier<Span> {
kind: RepetitionQuantifierKind,
span: Span,
}
#[cfg(test)]
#[allow(non_snake_case, unused)]
impl RepetitionQuantifier<()> {
fn ZeroOrOne() -> RepetitionQuantifier<()> {
RepetitionQuantifier {
kind: RepetitionQuantifierKind::ZeroOrOne,
span: (),
}
}
fn ZeroOrMore() -> RepetitionQuantifier<()> {
RepetitionQuantifier {
kind: RepetitionQuantifierKind::ZeroOrMore,
span: (),
}
}
fn OneOrMore() -> RepetitionQuantifier<()> {
RepetitionQuantifier {
kind: RepetitionQuantifierKind::OneOrMore,
span: (),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RepetitionQuantifierKind {
ZeroOrOne,
ZeroOrMore,
OneOrMore,
}
impl_spannable!(RepetitionQuantifierKind => RepetitionQuantifier);
#[cfg(test)]
mod tests {
use super::*;
macro_rules! check_macro_test {
(
$( #[ $meta:meta ] )*
$test_name:ident {
#[$kind:ident]
{
$( $tt:tt )*
}
}
) => {
$( #[ $meta ] )*
#[test]
fn $test_name() {
let tokens = quote! { $( $tt )* };
let ctxt = stringify!($kind).parse::<InvocationContext>().expect("Failed to parse `InvocationContext`");
check_macro(ctxt, tokens).expect("Macro check returned error");
}
};
}
check_macro_test! {
single_arm {
#[expr]
{
() => { a }
}
}
}
check_macro_test! {
accepts_final_semi {
#[expr]
{
() => { a };
}
}
}
check_macro_test! {
multiple_arms {
#[expr]
{
() => { a };
(()) => { b };
}
}
}
check_macro_test! {
#[should_panic = "Macro check returned error: InvalidProducedAst { span: 2, expected: [Return, Break, Ident, Self_, SelfUpper, Super, Crate, Fragment(Ident), ColonColon, LessThan, Literal, Fragment(Expr), If, LParen, LBracket, LBrace, Loop, While, For, Match], counter_example: [] }"]
empty_expr_is_not_an_expr {
#[expr]
{
() => {}
}
}
}
check_macro_test! {
just_a_simple_if {
#[expr]
{
() => { if a { a } }
}
}
}
check_macro_test! {
if_with_else {
#[expr]
{
() => { if a { a } else { a } }
}
}
}
check_macro_test! {
fn_call_1 {
#[expr]
{
() => { a(b) };
}
}
}
check_macro_test! {
fn_call_2 {
#[expr]
{
() => { a(b, c) };
}
}
}
check_macro_test! {
fn_call_3 {
#[expr]
{
() => { a(b, c, d) };
}
}
}
check_macro_test! {
fn_call_4 {
#[expr]
{
() => {
a(
b,
c,
d,
)
};
}
}
}
check_macro_test! {
fn_call_5 {
#[expr]
{
() => {
a(
b + c,
if d { e },
if f { g } else { h }
)
};
}
}
}
check_macro_test! {
fn_call_6 {
#[expr]
{
() => {
a(
b + c,
if d { e },
if f { g } else { h },
)
};
}
}
}
check_macro_test! {
array_0 {
#[expr]
{
() => { [] };
}
}
}
check_macro_test! {
array_1 {
#[expr]
{
() => { [a] };
}
}
}
check_macro_test! {
array_2 {
#[expr]
{
() => { [a, b] };
}
}
}
check_macro_test! {
array_2_ {
#[expr]
{
() => { [a, b,] };
}
}
}
check_macro_test! {
array_with_fragments {
#[expr]
{
(#a:expr, #b:expr, #c:ident, #d:ident) => { [#a, #b, #c, #d] };
}
}
}
check_macro_test! {
array_repeat {
#[expr]
{
( #( #a:expr )* ) => {
[ #( #a, )* ]
};
}
}
}
check_macro_test! {
path_repeat {
#[expr]
{
() => {
#( :: a )+
}
}
}
}
check_macro_test! {
path_repeat_from_fragment {
#[expr]
{
( #( #id:ident )+ ) => {
#( :: #id )+
}
}
}
}
}