Refactor to keep up with changes to proc_macro

Instead of a `kind` field containting a `TokenNode` variant, a
TokenTree is now an enum with variants of different types (Literal, Op,
Term, etc). Note that a TokenTree could be a sequence of TokenTrees if it is a
Group variant.

Other notes:

I'm unsure about the set_span call in Builder::emit_if, but I did not want to
throw away the passed in Span.

Parsing relies on destructuring references to the values associated with
TokenTree enum variants

It doesn't seem as easy to compose/chain TokenStreams as it is to
collect a Vec<TokenTree> into a TokenStream. There is probably some
iterator API I could use instead. See `match_arms` and build.rs

Refs 
This commit is contained in:
Coleman McFarland 2018-04-06 18:13:42 -07:00
parent 3d22f90ad7
commit 0f453a5568
3 changed files with 139 additions and 127 deletions
maud_macros/src

View file

@ -1,6 +1,5 @@
use proc_macro::{Delimiter, Literal, Span, TokenNode, TokenStream, TokenTree}; use proc_macro::{Delimiter, Group, Literal, Span, TokenStream, TokenTree};
use proc_macro::quote; use proc_macro::quote;
use maud_htmlescape::Escaper; use maud_htmlescape::Escaper;
pub struct Builder { pub struct Builder {
@ -24,7 +23,7 @@ impl Builder {
if !self.tail.is_empty() { if !self.tail.is_empty() {
let expr = { let expr = {
let output_ident = self.output_ident.clone(); let output_ident = self.output_ident.clone();
let string = TokenNode::Literal(Literal::string(&self.tail)); let string = TokenTree::Literal(Literal::string(&self.tail));
quote!($output_ident.push_str($string);) quote!($output_ident.push_str($string);)
}; };
self.stmts.push(expr); self.stmts.push(expr);
@ -35,7 +34,15 @@ impl Builder {
/// Reifies the `Builder` into a raw list of statements. /// Reifies the `Builder` into a raw list of statements.
pub fn build(mut self) -> TokenStream { pub fn build(mut self) -> TokenStream {
let Builder { stmts, .. } = { self.flush(); self }; let Builder { stmts, .. } = { self.flush(); self };
stmts.into_iter().collect()
// use a Group here?
let mut tts: Vec<TokenTree> = Vec::new();
for s in stmts.into_iter() {
let i = s.into_iter();
tts.extend(i);
}
tts.into_iter().collect()
} }
/// Pushes a statement, flushing the tail buffer in the process. /// Pushes a statement, flushing the tail buffer in the process.
@ -111,14 +118,20 @@ impl Builder {
) { ) {
// If the condition contains an opening brace `{`, // If the condition contains an opening brace `{`,
// wrap it in parentheses to avoid parse errors // wrap it in parentheses to avoid parse errors
if cond.clone().into_iter().any(|token| match token.kind { if cond.clone().into_iter().any(|token| match token {
TokenNode::Group(Delimiter::Brace, _) => true, TokenTree::Group(grp) => {
if grp.delimiter() == Delimiter::Brace {
true
} else {
false
}
},
_ => false, _ => false,
}) { }) {
cond = TokenStream::from(TokenTree { let mut g = Group::new(Delimiter::Parenthesis, cond);
kind: TokenNode::Group(Delimiter::Parenthesis, cond), // NOTE: Do we need to do this?
span: cond_span, g.set_span(cond_span);
}); cond = TokenStream::from(TokenTree::Group(g));
} }
self.push(quote!(if $cond { $body })); self.push(quote!(if $cond { $body }));
} }

View file

@ -1,4 +1,5 @@
#![feature(proc_macro)] #![feature(proc_macro)]
#![feature(pattern_parentheses)]
#![doc(html_root_url = "https://docs.rs/maud_macros/0.17.2")] #![doc(html_root_url = "https://docs.rs/maud_macros/0.17.2")]
@ -9,7 +10,7 @@ extern crate proc_macro;
mod parse; mod parse;
mod build; mod build;
use proc_macro::{Literal, Span, Term, TokenNode, TokenStream, TokenTree}; use proc_macro::{Span, Term, TokenStream, TokenTree};
use proc_macro::quote; use proc_macro::quote;
type ParseResult<T> = Result<T, String>; type ParseResult<T> = Result<T, String>;
@ -27,21 +28,21 @@ pub fn html_debug(input: TokenStream) -> TokenStream {
} }
fn expand(input: TokenStream) -> TokenStream { fn expand(input: TokenStream) -> TokenStream {
let output_ident = TokenTree {
kind: TokenNode::Term(Term::intern("__maud_output")), let output_ident = TokenTree::Term(Term::new("__maud_output", Span::def_site()));
span: Span::def_site(),
};
// Heuristic: the size of the resulting markup tends to correlate with the // Heuristic: the size of the resulting markup tends to correlate with the
// code size of the template itself // code size of the template itself
let size_hint = input.to_string().len(); //
let size_hint = TokenNode::Literal(Literal::u64(size_hint as u64)); // NOTE: can't get this to compile inside quote!
//let size_hint = Literal::u64_unsuffixed(size_hint as u64);
let stmts = match parse::parse(input, output_ident.clone()) { let stmts = match parse::parse(input, output_ident.clone()) {
Ok(stmts) => stmts, Ok(stmts) => stmts,
Err(e) => panic!(e), Err(e) => panic!(e),
}; };
quote!({ quote!({
extern crate maud; extern crate maud;
let mut $output_ident = String::with_capacity($size_hint as usize); //let mut $output_ident = String::with_capacity($size_hint as usize);
let mut $output_ident = String::new();
$stmts $stmts
maud::PreEscaped($output_ident) maud::PreEscaped($output_ident)
}) })

View file

@ -1,14 +1,15 @@
use proc_macro::{ use proc_macro::{
Delimiter, Delimiter,
Group,
Literal, Literal,
Spacing, Spacing,
Span, Span,
TokenNode, Term,
TokenStream, TokenStream,
TokenTree, TokenTree,
TokenTreeIter,
}; };
use std::iter;
use proc_macro::token_stream;
use std::mem; use std::mem;
use literalext::LiteralExt; use literalext::LiteralExt;
@ -28,7 +29,7 @@ struct Parser {
output_ident: TokenTree, output_ident: TokenTree,
/// Indicates whether we're inside an attribute node. /// Indicates whether we're inside an attribute node.
in_attr: bool, in_attr: bool,
input: TokenTreeIter, input: token_stream::IntoIter,
} }
impl Iterator for Parser { impl Iterator for Parser {
@ -97,24 +98,18 @@ impl Parser {
loop { loop {
match self.peek2() { match self.peek2() {
None => return Ok(()), None => return Ok(()),
Some((TokenTree { kind: TokenNode::Op(';', _), .. }, _)) => self.advance(), Some((TokenTree::Op(o), _)) if o.op() == ';' => self.advance(),
Some(( Some((TokenTree::Op(o), Some(TokenTree::Term(term)))) if o.op() == '@' && term.as_str() == "let" => {
TokenTree { kind: TokenNode::Op('@', _), .. },
Some(TokenTree { kind: TokenNode::Term(term), span }),
)) if term.as_str() == "let" => {
// When emitting a `@let`, wrap the rest of the block in a // When emitting a `@let`, wrap the rest of the block in a
// new block to avoid scoping issues // new block to avoid scoping issues
let keyword = TokenTree { kind: TokenNode::Term(term), span }; let keyword = Term::new(term.as_str(), term.span());
self.advance2(); self.advance2();
builder.push({ builder.push({
let mut builder = self.builder(); let mut builder = self.builder();
builder.push(keyword); builder.push(TokenTree::Term(keyword));
self.let_expr(&mut builder)?; self.let_expr(&mut builder)?;
self.markups(&mut builder)?; self.markups(&mut builder)?;
TokenTree { TokenTree::Group(Group::new(Delimiter::Brace, builder.build()))
kind: TokenNode::Group(Delimiter::Brace, builder.build()),
span,
}
}); });
}, },
_ => self.markup(builder)?, _ => self.markup(builder)?,
@ -130,17 +125,17 @@ impl Parser {
}; };
match token { match token {
// Literal // Literal
TokenTree { kind: TokenNode::Literal(lit), .. } => { TokenTree::Literal(lit) => {
self.advance(); self.advance();
self.literal(lit, builder)?; self.literal(lit, builder)?;
}, },
// Special form // Special form
TokenTree { kind: TokenNode::Op('@', _), .. } => { TokenTree::Op(o) if o.op() == '@' => {
self.advance(); self.advance();
match self.next() { match self.next() {
Some(TokenTree { kind: TokenNode::Term(term), span }) => { Some(TokenTree::Term(term)) => {
let keyword = TokenTree { kind: TokenNode::Term(term), span }; let keyword = Term::new(term.as_str(), term.span());
builder.push(keyword); builder.push(TokenTree::Term(keyword));
match term.as_str() { match term.as_str() {
"if" => self.if_expr(builder)?, "if" => self.if_expr(builder)?,
"while" => self.while_expr(builder)?, "while" => self.while_expr(builder)?,
@ -152,21 +147,21 @@ impl Parser {
}, },
_ => return self.error("expected keyword after `@`"), _ => return self.error("expected keyword after `@`"),
} }
} },
// Element // Element
TokenTree { kind: TokenNode::Term(_), .. } => { TokenTree::Term(_) => {
let name = self.namespaced_name()?; let name = self.namespaced_name()?;
self.element(&name, builder)?; self.element(&name, builder)?;
}, },
// Splice // Splice
TokenTree { kind: TokenNode::Group(Delimiter::Parenthesis, expr), .. } => { TokenTree::Group(ref grp) if grp.delimiter() == Delimiter::Parenthesis => {
self.advance(); self.advance();
builder.splice(expr); builder.splice(grp.stream());
} },
// Block // Block
TokenTree { kind: TokenNode::Group(Delimiter::Brace, block), .. } => { TokenTree::Group(ref grp) if grp.delimiter() == Delimiter::Brace => {
self.advance(); self.advance();
self.with_input(block).markups(builder)?; self.with_input(grp.stream()).markups(builder)?;
}, },
// ??? // ???
_ => return self.error("invalid syntax"), _ => return self.error("invalid syntax"),
@ -190,8 +185,9 @@ impl Parser {
fn if_expr(&mut self, builder: &mut Builder) -> ParseResult<()> { fn if_expr(&mut self, builder: &mut Builder) -> ParseResult<()> {
loop { loop {
match self.next() { match self.next() {
Some(TokenTree { kind: TokenNode::Group(Delimiter::Brace, block), span }) => { Some(TokenTree::Group(ref block))
let block = self.block(block, span)?; if block.delimiter() == Delimiter::Brace => {
let block = self.block(block.stream(), block.span())?;
builder.push(block); builder.push(block);
break; break;
}, },
@ -207,30 +203,28 @@ impl Parser {
/// The leading `@else if` or `@else` should *not* already be consumed. /// The leading `@else if` or `@else` should *not* already be consumed.
fn else_if_expr(&mut self, builder: &mut Builder) -> ParseResult<()> { fn else_if_expr(&mut self, builder: &mut Builder) -> ParseResult<()> {
match self.peek2() { match self.peek2() {
// Try to match an `@else` after this Some((TokenTree::Op(o), Some(TokenTree::Term(else_keyword))))
Some(( if o.op() == '@' && else_keyword.as_str() == "else" => {
TokenTree { kind: TokenNode::Op('@', _), .. },
Some(TokenTree { kind: TokenNode::Term(else_keyword), span }),
)) if else_keyword.as_str() == "else" => {
self.advance2(); self.advance2();
let else_keyword = TokenTree { kind: TokenNode::Term(else_keyword), span }; let else_keyword = Term::new("else", else_keyword.span());
builder.push(else_keyword); builder.push(TokenTree::Term(else_keyword));
match self.peek() { match self.peek() {
// `@else if` // `@else if`
Some(TokenTree { kind: TokenNode::Term(if_keyword), span }) Some(TokenTree::Term(if_keyword)) if if_keyword.as_str() == "if" => {
if if_keyword.as_str() == "if" => {
self.advance(); self.advance();
let if_keyword = TokenTree { kind: TokenNode::Term(if_keyword), span }; let if_keyword = Term::new("if", if_keyword.span());
builder.push(if_keyword); builder.push(TokenTree::Term(if_keyword));
self.if_expr(builder)?; self.if_expr(builder)?;
}, },
// Just an `@else` // Just an `@else`
_ => { _ => {
if let Some(TokenTree { kind: TokenNode::Group(Delimiter::Brace, block), span }) = self.next() { // match brace `{`
let block = self.block(block, span)?; match self.next() {
builder.push(block); Some(TokenTree::Group(ref grp)) if grp.delimiter() == Delimiter::Brace => {
} else { let block = self.block(grp.stream(), grp.span())?;
return self.error("expected body for @else"); builder.push(block);
},
_ => { return self.error("expected body for @else"); },
} }
}, },
} }
@ -247,8 +241,8 @@ impl Parser {
fn while_expr(&mut self, builder: &mut Builder) -> ParseResult<()> { fn while_expr(&mut self, builder: &mut Builder) -> ParseResult<()> {
loop { loop {
match self.next() { match self.next() {
Some(TokenTree { kind: TokenNode::Group(Delimiter::Brace, block), span }) => { Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => {
let block = self.block(block, span)?; let block = self.block(block.stream(), block.span())?;
builder.push(block); builder.push(block);
break; break;
}, },
@ -265,8 +259,9 @@ impl Parser {
fn for_expr(&mut self, builder: &mut Builder) -> ParseResult<()> { fn for_expr(&mut self, builder: &mut Builder) -> ParseResult<()> {
loop { loop {
match self.next() { match self.next() {
Some(TokenTree { kind: TokenNode::Term(in_keyword), span }) if in_keyword.as_str() == "in" => { Some(TokenTree::Term(in_keyword)) if in_keyword.as_str() == "in" => {
builder.push(TokenTree { kind: TokenNode::Term(in_keyword), span }); let in_keyword = Term::new("in", in_keyword.span());
builder.push(TokenTree::Term(in_keyword));
break; break;
}, },
Some(token) => builder.push(token), Some(token) => builder.push(token),
@ -275,8 +270,8 @@ impl Parser {
} }
loop { loop {
match self.next() { match self.next() {
Some(TokenTree { kind: TokenNode::Group(Delimiter::Brace, block), span }) => { Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => {
let block = self.block(block, span)?; let block = self.block(block.stream(), block.span())?;
builder.push(block); builder.push(block);
break; break;
}, },
@ -293,12 +288,10 @@ impl Parser {
fn match_expr(&mut self, builder: &mut Builder) -> ParseResult<()> { fn match_expr(&mut self, builder: &mut Builder) -> ParseResult<()> {
loop { loop {
match self.next() { match self.next() {
Some(TokenTree { kind: TokenNode::Group(Delimiter::Brace, body), span }) => { Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => {
let body = self.with_input(body).match_arms()?; let body = self.with_input(body.stream()).match_arms()?;
builder.push(TokenTree { let body = Group::new(Delimiter::Brace, body);
kind: TokenNode::Group(Delimiter::Brace, body), builder.push(TokenTree::Group(body));
span,
});
break; break;
}, },
Some(token) => builder.push(token), Some(token) => builder.push(token),
@ -309,24 +302,22 @@ impl Parser {
} }
fn match_arms(&mut self) -> ParseResult<TokenStream> { fn match_arms(&mut self) -> ParseResult<TokenStream> {
let mut arms = Vec::new(); let mut arms: Vec<TokenTree> = Vec::new();
while let Some(arm) = self.match_arm()? { while let Some(arm) = self.match_arm()? {
arms.push(arm); arms.extend(arm);
} }
Ok(arms.into_iter().collect()) Ok(arms.into_iter().collect())
} }
fn match_arm(&mut self) -> ParseResult<Option<TokenStream>> { fn match_arm(&mut self) -> ParseResult<Option<Vec<TokenTree>>> {
let mut pat = Vec::new(); let mut pat: Vec<TokenTree> = Vec::new();
loop { loop {
match self.peek2() { match self.peek2() {
Some(( Some((TokenTree::Op(eq), Some(TokenTree::Op(gt))))
eq @ TokenTree { kind: TokenNode::Op('=', Spacing::Joint), .. }, if eq.op() == '=' && gt.op() == '>' && eq.spacing() == Spacing::Joint => {
Some(gt @ TokenTree { kind: TokenNode::Op('>', _), .. }),
)) => {
self.advance2(); self.advance2();
pat.push(eq); pat.push(TokenTree::Op(eq));
pat.push(gt); pat.push(TokenTree::Op(gt));
break; break;
}, },
Some((token, _)) => { Some((token, _)) => {
@ -343,23 +334,25 @@ impl Parser {
} }
let body = match self.next() { let body = match self.next() {
// $pat => { $stmts } // $pat => { $stmts }
Some(TokenTree { kind: TokenNode::Group(Delimiter::Brace, body), span }) => { Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => {
let body = self.block(body, span)?; let body: TokenTree = self.block(body.stream(), body.span())?;
// Trailing commas are optional if the match arm is a braced block // Trailing commas are optional if the match arm is a braced block
if let Some(TokenTree { kind: TokenNode::Op(',', _), .. }) = self.peek() { if let Some(TokenTree::Op(o)) = self.peek() {
self.advance(); if o.op() == ',' {
self.advance();
}
} }
body body
}, },
// $pat => $expr // $pat => $expr
Some(first_token) => { Some(first_token) => {
let mut span = first_token.span; let mut span = first_token.span();
let mut body = vec![first_token]; let mut body = vec![first_token];
loop { loop {
match self.next() { match self.next() {
Some(TokenTree { kind: TokenNode::Op(',', _), .. }) => break, Some(TokenTree::Op(o)) if o.op() == ',' => break,
Some(token) => { Some(token) => {
if let Some(bigger_span) = span.join(token.span) { if let Some(bigger_span) = span.join(token.span()) {
span = bigger_span; span = bigger_span;
} }
body.push(token); body.push(token);
@ -371,7 +364,8 @@ impl Parser {
}, },
None => return self.error("unexpected end of @match arm"), None => return self.error("unexpected end of @match arm"),
}; };
Ok(Some(pat.into_iter().chain(iter::once(body)).collect())) pat.push(body);
Ok(Some(pat))
} }
/// Parses and renders a `@let` expression. /// Parses and renders a `@let` expression.
@ -380,21 +374,29 @@ impl Parser {
fn let_expr(&mut self, builder: &mut Builder) -> ParseResult<()> { fn let_expr(&mut self, builder: &mut Builder) -> ParseResult<()> {
loop { loop {
match self.next() { match self.next() {
Some(token @ TokenTree { kind: TokenNode::Op('=', _), .. }) => { Some(token) => {
builder.push(token); match token {
break; TokenTree::Op(ref o) if o.op() == '=' => {
builder.push(token.clone());
break;
}
_ => builder.push(token),
}
}, },
Some(token) => builder.push(token),
None => return self.error("unexpected end of @let expression"), None => return self.error("unexpected end of @let expression"),
} }
} }
loop { loop {
match self.next() { match self.next() {
Some(token @ TokenTree { kind: TokenNode::Op(';', _), .. }) => { Some(token) => {
builder.push(token); match token {
break; TokenTree::Op(ref o) if o.op() == ';' => {
builder.push(token.clone());
break;
},
_ => builder.push(token),
}
}, },
Some(token) => builder.push(token),
None => return self.error("unexpected end of @let expression"), None => return self.error("unexpected end of @let expression"),
} }
} }
@ -412,8 +414,7 @@ impl Parser {
self.attrs(builder)?; self.attrs(builder)?;
builder.element_open_end(); builder.element_open_end();
match self.peek() { match self.peek() {
Some(TokenTree { kind: TokenNode::Op(';', _), .. }) | Some(TokenTree::Op(o)) if o.op() == ';' || o.op() == '/' => {
Some(TokenTree { kind: TokenNode::Op('/', _), .. }) => {
// Void element // Void element
self.advance(); self.advance();
}, },
@ -436,7 +437,7 @@ impl Parser {
let token_after = attempt.next(); let token_after = attempt.next();
match (maybe_name, token_after) { match (maybe_name, token_after) {
// Non-empty attribute // Non-empty attribute
(Ok(name), Some(TokenTree { kind: TokenNode::Op('=', _), .. })) => { (Ok(ref name), Some(TokenTree::Op(ref o))) if o.op() == '=' => {
self.commit(attempt); self.commit(attempt);
builder.attribute_start(&name); builder.attribute_start(&name);
{ {
@ -448,7 +449,7 @@ impl Parser {
builder.attribute_end(); builder.attribute_end();
}, },
// Empty attribute // Empty attribute
(Ok(name), Some(TokenTree { kind: TokenNode::Op('?', _), .. })) => { (Ok(ref name), Some(TokenTree::Op(ref o))) if o.op() == '?' => {
self.commit(attempt); self.commit(attempt);
if let Some((cond, cond_span)) = self.attr_toggler() { if let Some((cond, cond_span)) = self.attr_toggler() {
// Toggle the attribute based on a boolean expression // Toggle the attribute based on a boolean expression
@ -464,7 +465,7 @@ impl Parser {
} }
}, },
// Class shorthand // Class shorthand
(Err(_), Some(TokenTree { kind: TokenNode::Op('.', _), .. })) => { (Err(_), Some(TokenTree::Op(o))) if o.op() == '.' => {
self.commit(attempt); self.commit(attempt);
let class_name = self.name()?; let class_name = self.name()?;
if let Some((cond, cond_span)) = self.attr_toggler() { if let Some((cond, cond_span)) = self.attr_toggler() {
@ -476,7 +477,7 @@ impl Parser {
} }
}, },
// ID shorthand // ID shorthand
(Err(_), Some(TokenTree { kind: TokenNode::Op('#', _), .. })) => { (Err(_), Some(TokenTree::Op(o))) if o.op() == '#' => {
self.commit(attempt); self.commit(attempt);
ids.push(self.name()?); ids.push(self.name()?);
}, },
@ -512,20 +513,18 @@ impl Parser {
/// Parses the `[cond]` syntax after an empty attribute or class shorthand. /// Parses the `[cond]` syntax after an empty attribute or class shorthand.
fn attr_toggler(&mut self) -> Option<(TokenStream, Span)> { fn attr_toggler(&mut self) -> Option<(TokenStream, Span)> {
if let Some(TokenTree { match self.peek() {
kind: TokenNode::Group(Delimiter::Bracket, cond), Some(TokenTree::Group(ref grp)) if grp.delimiter() == Delimiter::Bracket => {
span: delim_span, self.advance();
}) = self.peek() { Some((grp.stream(), grp.span()))
self.advance(); }
Some((cond, delim_span)) _ => None
} else {
None
} }
} }
/// Parses an identifier, without dealing with namespaces. /// Parses an identifier, without dealing with namespaces.
fn name(&mut self) -> ParseResult<String> { fn name(&mut self) -> ParseResult<String> {
let mut s = if let Some(TokenTree { kind: TokenNode::Term(term), .. }) = self.peek() { let mut s = if let Some(TokenTree::Term(term)) = self.peek() {
self.advance(); self.advance();
String::from(term.as_str()) String::from(term.as_str())
} else { } else {
@ -534,12 +533,12 @@ impl Parser {
let mut expect_ident = false; let mut expect_ident = false;
loop { loop {
expect_ident = match self.peek() { expect_ident = match self.peek() {
Some(TokenTree { kind: TokenNode::Op('-', _), .. }) => { Some(TokenTree::Op(o)) if o.op() == '-' => {
self.advance(); self.advance();
s.push('-'); s.push('-');
true true
}, },
Some(TokenTree { kind: TokenNode::Term(term), .. }) if expect_ident => { Some(TokenTree::Term(term)) if expect_ident => {
self.advance(); self.advance();
s.push_str(term.as_str()); s.push_str(term.as_str());
false false
@ -554,22 +553,21 @@ impl Parser {
/// if necessary. /// if necessary.
fn namespaced_name(&mut self) -> ParseResult<String> { fn namespaced_name(&mut self) -> ParseResult<String> {
let mut s = self.name()?; let mut s = self.name()?;
if let Some(TokenTree { kind: TokenNode::Op(':', _), .. }) = self.peek() { if let Some(TokenTree::Op(o)) = self.peek() {
self.advance(); if o.op() == ':' {
s.push(':'); self.advance();
s.push_str(&self.name()?); s.push(':');
s.push_str(&self.name()?);
}
} }
Ok(s) Ok(s)
} }
/// Parses the given token stream as a Maud expression, returning a block of /// Parses the given token stream as a Maud expression, returning a block of
/// Rust code. /// Rust code.
fn block(&mut self, body: TokenStream, span: Span) -> ParseResult<TokenTree> { fn block(&mut self, body: TokenStream, _span: Span) -> ParseResult<TokenTree> {
let mut builder = self.builder(); let mut builder = self.builder();
self.with_input(body).markups(&mut builder)?; self.with_input(body).markups(&mut builder)?;
Ok(TokenTree { Ok(TokenTree::Group(Group::new(Delimiter::Brace, builder.build())))
kind: TokenNode::Group(Delimiter::Brace, builder.build()),
span,
})
} }
} }