266 lines
8.1 KiB
Rust
Raw Normal View History

// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! # Quasiquoter
2017-06-05 01:41:33 +00:00
//! This file contains the implementation internals of the quasiquoter provided by `quote!`.
//! This quasiquoter uses macros 2.0 hygiene to reliably access
//! items from `proc_macro`, to build a `proc_macro::TokenStream`.
use {Delimiter, Literal, Spacing, Span, Term, TokenNode, TokenStream, TokenTree};
use syntax::ext::base::{ExtCtxt, ProcMacro};
use syntax::parse::token;
use syntax::tokenstream;
pub struct Quoter;
pub fn unquote<T: Into<TokenStream> + Clone>(tokens: &T) -> TokenStream {
T::into(tokens.clone())
}
2017-06-05 01:41:33 +00:00
pub trait Quote {
fn quote(self) -> TokenStream;
}
macro_rules! quote_tok {
(,) => { TokenNode::Op(',', Spacing::Alone) };
(.) => { TokenNode::Op('.', Spacing::Alone) };
(:) => { TokenNode::Op(':', Spacing::Alone) };
(::) => {
[
TokenNode::Op(':', Spacing::Joint),
TokenNode::Op(':', Spacing::Alone)
].iter().cloned().collect::<TokenStream>()
};
(!) => { TokenNode::Op('!', Spacing::Alone) };
(<) => { TokenNode::Op('<', Spacing::Alone) };
(>) => { TokenNode::Op('>', Spacing::Alone) };
(_) => { TokenNode::Op('_', Spacing::Alone) };
(0) => { TokenNode::Literal(::Literal::integer(0)) };
(&) => { TokenNode::Op('&', Spacing::Alone) };
($i:ident) => { TokenNode::Term(Term::intern(stringify!($i))) };
}
macro_rules! quote_tree {
((unquote $($t:tt)*)) => { $($t)* };
((quote $($t:tt)*)) => { ($($t)*).quote() };
(($($t:tt)*)) => { TokenNode::Group(Delimiter::Parenthesis, quote!($($t)*)) };
([$($t:tt)*]) => { TokenNode::Group(Delimiter::Bracket, quote!($($t)*)) };
({$($t:tt)*}) => { TokenNode::Group(Delimiter::Brace, quote!($($t)*)) };
($t:tt) => { quote_tok!($t) };
}
macro_rules! quote {
() => { TokenStream::empty() };
($($t:tt)*) => {
[
$(TokenStream::from(quote_tree!($t)),)*
].iter().cloned().collect::<TokenStream>()
};
}
impl ProcMacro for Quoter {
fn expand<'cx>(&self, cx: &'cx mut ExtCtxt,
_: ::syntax_pos::Span,
stream: tokenstream::TokenStream)
-> tokenstream::TokenStream {
let mut info = cx.current_expansion.mark.expn_info().unwrap();
info.callee.allow_internal_unstable = true;
cx.current_expansion.mark.set_expn_info(info);
::__internal::set_sess(cx, || TokenStream(stream).quote().0)
}
}
impl<T: Quote> Quote for Option<T> {
fn quote(self) -> TokenStream {
match self {
Some(t) => quote!(Some((quote t))),
None => quote!(None),
}
}
}
impl Quote for TokenStream {
fn quote(self) -> TokenStream {
if self.is_empty() {
return quote!(::TokenStream::empty());
}
let mut after_dollar = false;
let tokens = self.into_iter().filter_map(|tree| {
if after_dollar {
after_dollar = false;
match tree.kind {
TokenNode::Term(_) => {
return Some(quote!(::__internal::unquote(&(unquote tree)),));
}
TokenNode::Op('$', _) => {}
_ => panic!("`$` must be followed by an ident or `$` in `quote!`"),
}
} else if let TokenNode::Op('$', _) = tree.kind {
after_dollar = true;
return None;
}
Some(quote!(::TokenStream::from((quote tree)),))
}).collect::<TokenStream>();
if after_dollar {
panic!("unexpected trailing `$` in `quote!`");
}
quote!([(unquote tokens)].iter().cloned().collect::<::TokenStream>())
}
}
impl Quote for TokenTree {
fn quote(self) -> TokenStream {
quote!(::TokenTree { span: (quote self.span), kind: (quote self.kind) })
}
}
impl Quote for TokenNode {
fn quote(self) -> TokenStream {
macro_rules! gen_match {
($($i:ident($($arg:ident),+)),*) => {
match self {
$(TokenNode::$i($($arg),+) => quote! {
::TokenNode::$i($((quote $arg)),+)
},)*
}
}
}
gen_match! { Op(op, kind), Group(delim, tokens), Term(term), Literal(lit) }
}
}
impl Quote for char {
fn quote(self) -> TokenStream {
TokenNode::Literal(Literal::character(self)).into()
}
}
impl<'a> Quote for &'a str {
fn quote(self) -> TokenStream {
TokenNode::Literal(Literal::string(self)).into()
}
}
impl Quote for usize {
fn quote(self) -> TokenStream {
TokenNode::Literal(Literal::integer(self as i128)).into()
}
}
impl Quote for Term {
fn quote(self) -> TokenStream {
quote!(::Term::intern((quote self.as_str())))
}
}
impl Quote for Span {
fn quote(self) -> TokenStream {
quote!(::Span::default())
}
}
macro_rules! literals {
($($i:ident),*; $($raw:ident),*) => {
pub enum LiteralKind {
$($i,)*
$($raw(usize),)*
}
impl LiteralKind {
pub fn with_contents_and_suffix(self, contents: Term, suffix: Option<Term>)
-> Literal {
let contents = contents.0;
let suffix = suffix.map(|t| t.0);
match self {
$(LiteralKind::$i => {
Literal(token::Literal(token::Lit::$i(contents), suffix))
})*
$(LiteralKind::$raw(n) => {
Literal(token::Literal(token::Lit::$raw(contents, n), suffix))
})*
}
}
}
impl Literal {
fn kind_contents_and_suffix(self) -> (LiteralKind, Term, Option<Term>) {
let (lit, suffix) = match self.0 {
token::Literal(lit, suffix) => (lit, suffix),
_ => panic!("unsupported literal {:?}", self.0),
};
let (kind, contents) = match lit {
$(token::Lit::$i(contents) => (LiteralKind::$i, contents),)*
$(token::Lit::$raw(contents, n) => (LiteralKind::$raw(n), contents),)*
};
(kind, Term(contents), suffix.map(Term))
}
}
impl Quote for LiteralKind {
fn quote(self) -> TokenStream {
match self {
$(LiteralKind::$i => quote! {
::__internal::LiteralKind::$i
},)*
$(LiteralKind::$raw(n) => quote! {
::__internal::LiteralKind::$raw((quote n))
},)*
}
}
}
impl Quote for Literal {
fn quote(self) -> TokenStream {
let (kind, contents, suffix) = self.kind_contents_and_suffix();
quote! {
(quote kind).with_contents_and_suffix((quote contents), (quote suffix))
}
}
}
}
}
literals!(Byte, Char, Float, Str_, Integer, ByteStr; StrRaw, ByteStrRaw);
impl Quote for Delimiter {
fn quote(self) -> TokenStream {
macro_rules! gen_match {
($($i:ident),*) => {
match self {
$(Delimiter::$i => { quote!(::Delimiter::$i) })*
}
}
}
gen_match!(Parenthesis, Brace, Bracket, None)
}
}
impl Quote for Spacing {
fn quote(self) -> TokenStream {
macro_rules! gen_match {
($($i:ident),*) => {
match self {
$(Spacing::$i => { quote!(::Spacing::$i) })*
}
}
}
gen_match!(Alone, Joint)
}
}