1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! Debug utilities for token stream inspection
use crate::{Parser, Result, TokenIter};
// When debug_grammar feature is enabled (or generating docs), implement the full debug functionality
#[cfg(any(doc, feature = "debug_grammar"))]
mod enabled {
use super::*;
use crate::IntoTokenIter;
#[cfg(not(feature = "proc_macro2"))]
use proc_macro::{Delimiter, TokenTree};
#[cfg(feature = "proc_macro2")]
use proc_macro2::{Delimiter, TokenTree};
/// A debug parser that prints the typename of `T` and the next `N` tokens to stderr.
///
/// This parser clones the token iterator to peek ahead without consuming tokens,
/// making it useful for debugging parsers without affecting the actual parsing.
///
/// Output format:
/// - Line 1: `Type: typename`
/// - Line 2: `Source: literal token representation`
///
/// If there are more tokens than `N`, the output ends with ` …`.
///
/// # Type Parameters
///
/// * `T` - The type to parse (typename will be printed)
/// * `N` - The number of tokens to print (default: 5)
///
/// # Example
///
/// ```rust,ignore
/// # use unsynn::*;
/// let mut tokens = "fn foo ( bar : i32 ) { }".to_token_iter();
///
/// // This will print to stderr:
/// // Type: proc_macro2::Ident
/// // Source: fn foo (bar : i32) …
/// let result: StderrLog<Ident, 3> = tokens.parse().unwrap();
///
/// // The actual Ident was also parsed
/// assert_eq!(result.value.to_string(), "fn");
/// ```
pub struct StderrLog<T, const N: usize = 5> {
/// The parsed value
pub value: T,
}
// we have only debug side-effects here
#[mutants::skip]
impl<T: Parser, const N: usize> Parser for StderrLog<T, N> {
fn parser(tokens: &mut TokenIter) -> Result<Self> {
// Print the typename
let typename = std::any::type_name::<T>();
eprintln!("Type: {}", typename);
// Clone the iterator to peek without consuming
let mut peek_iter = tokens.clone();
// Collect and print the next N tokens
let mut output = String::new();
let mut count = 0;
let mut has_more = false;
collect_tokens(&mut peek_iter, N, &mut output, &mut count, &mut has_more);
// Print the second line with tokens
eprint!("Source: {}", output);
if has_more {
eprintln!(" …");
} else {
eprintln!();
}
// Now actually parse T
let value = T::parser(tokens)?;
Ok(StderrLog { value })
}
}
/// Helper function to collect tokens into a string representation
#[mutants::skip]
fn collect_tokens(
tokens: &mut TokenIter,
mut remaining: usize,
output: &mut String,
count: &mut usize,
has_more: &mut bool,
) {
while remaining > 0 {
if let Some(token) = tokens.next() {
*count += 1;
remaining -= 1;
match &token {
TokenTree::Group(group) => {
let (open, close) = match group.delimiter() {
Delimiter::Parenthesis => ("(", ")"),
Delimiter::Brace => ("{", "}"),
Delimiter::Bracket => ("[", "]"),
Delimiter::None => ("", ""),
};
output.push_str(open);
// Recurse into the group
let group_stream = group.stream();
let mut group_iter = group_stream.into_iter().into_token_iter();
collect_tokens(&mut group_iter, remaining, output, count, has_more);
output.push_str(close);
}
TokenTree::Ident(ident) => {
if !output.is_empty()
&& !output.ends_with(|c: char| matches!(c, '(' | '[' | '{' | ' '))
{
output.push(' ');
}
output.push_str(&ident.to_string());
}
TokenTree::Punct(punct) => {
let ch = punct.as_char();
// Add space before certain punctuation for readability
if !output.is_empty()
&& !output.ends_with(|c: char| matches!(c, '(' | '[' | '{' | ' '))
&& matches!(
ch,
':' | '='
| '<'
| '>'
| '!'
| '&'
| '|'
| '+'
| '-'
| '*'
| '/'
| '%'
)
{
output.push(' ');
}
output.push(ch);
}
TokenTree::Literal(literal) => {
if !output.is_empty()
&& !output.ends_with(|c: char| matches!(c, '(' | '[' | '{' | ' '))
{
output.push(' ');
}
output.push_str(&literal.to_string());
}
}
} else {
return;
}
}
// Check if there are more tokens
if tokens.clone().next().is_some() {
*has_more = true;
}
}
}
// When debug_grammar feature is disabled, provide a no-op implementation
#[cfg(not(any(doc, feature = "debug_grammar")))]
mod disabled {
use super::{Parser, Result, TokenIter};
use std::marker::PhantomData;
/// A no-op debug parser that does nothing when the `debug_grammar` feature is disabled.
///
/// This type becomes a zero-sized type that doesn't parse anything and has no runtime cost.
/// It exists only to maintain API compatibility when debug output is disabled.
pub struct StderrLog<T, const N: usize = 5>(PhantomData<T>);
#[mutants::skip]
impl<T, const N: usize> Parser for StderrLog<T, N> {
#[inline]
fn parser(_tokens: &mut TokenIter) -> Result<Self> {
// Complete no-op: don't parse T, don't consume tokens
Ok(StderrLog(PhantomData))
}
}
#[mutants::skip]
impl<T, const N: usize> crate::ToTokens for StderrLog<T, N> {
#[inline]
fn to_tokens(&self, _tokens: &mut crate::TokenStream) {
// No-op: emit nothing
}
}
}
// Re-export the appropriate implementation
#[cfg(any(doc, feature = "debug_grammar"))]
pub use enabled::*;
#[cfg(not(any(doc, feature = "debug_grammar")))]
pub use disabled::*;