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 #121
This commit is contained in:
parent
3d22f90ad7
commit
0f453a5568
3 changed files with 139 additions and 127 deletions
|
@ -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 }));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue