synthax 0.4.1

Synthesize syntax with quasiquoting plugins.
// Copyright 2016 Kyle Mayes
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use syntax::ast::*;
use syntax::codemap::{ExpnId, Span};
use syntax::ext::base::{ExtCtxt};
use syntax::ext::build::{AstBuilder};
use syntax::ext::quote::rt::{BytePos};
use syntax::parse::token::{self, BinOpToken, DelimToken, Token};
use syntax::ptr::{P};
use syntax::symbol::{Symbol};
use syntax::tokenstream::{Delimited, TokenTree};

//================================================
// Macros
//================================================

macro_rules! ast { ($($ident:expr), +) => (&["syntax", "ast", $($ident), *]); }
macro_rules! codemap { ($($ident:expr), +) => (&["syntax", "codemap", $($ident), *]); }
macro_rules! rt { ($($ident:expr), +) => (&["syntax", "ext", "quote", "rt", $($ident), *]); }
macro_rules! symbol { ($($ident:expr), +) => (&["syntax", "symbol", $($ident), *]); }
macro_rules! token { ($($ident:expr), +) => (&["syntax", "parse", "token", $($ident), *]); }
macro_rules! tokenstream { ($($ident:expr), +) => (&["syntax", "tokenstream", $($ident), *]); }

// exprs! ________________________________________

/// Converts the supplied values into expressions.
macro_rules! exprs {
    ($context:expr, $span:expr, [$($expr:expr), +]) => ({
        vec![$($expr.to_expr($context, $span)), +]
    });
}

// fields! _______________________________________

/// Converts the supplied fields into expressions.
macro_rules! fields {
    ($context:expr, $span:expr, $struct_:expr, [$($field:ident), +]) => ({
        vec![$(mk_field($context, $span, stringify!($field), $struct_.$field.to_expr($context, $span))), +]
    });
}

// to_expr_primitive! ____________________________

/// Implements `ToExpr` for a primitive type.
macro_rules! to_expr_primitive {
    ($ty:ty, $method:ident) => (
        impl ToExpr for $ty {
            fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
                context.$method(span, *self)
            }
        }
    )
}

//================================================
// Traits
//================================================

// ToExpr ________________________________________

/// An AST type whose values can be generated by evaluating expressions.
pub trait ToExpr {
    /// Returns an expression which would produce this value if evaluated.
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr>;
}

to_expr_primitive!(u32, expr_u32);
to_expr_primitive!(usize, expr_usize);

impl<'a> ToExpr for &'a str {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        context.expr_str(span, Symbol::intern(self))
    }
}

impl<T: ToExpr> ToExpr for Option<T> {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        match *self {
            Some(ref some) => context.expr_some(span, some.to_expr(context, span)),
            None => context.expr_none(span),
        }
    }
}

impl<T: ToExpr> ToExpr for Vec<T> {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        let exprs = self.iter().map(|e| e.to_expr(context, span)).collect();
        mk_expr_method_call(context, span, context.expr_vec(span, exprs), "to_vec", vec![])
    }
}

impl ToExpr for String {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        mk_expr_method_call(context, span, (&self[..]).to_expr(context, span), "to_string", vec![])
    }
}

// Span

impl ToExpr for BytePos {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        mk_expr_call(context, span, rt!["BytePos"], vec![self.0.to_expr(context, span)])
    }
}

impl ToExpr for ExpnId {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        mk_expr_call(context, span, codemap!["ExpnId"], vec![self.0.to_expr(context, span)])
    }
}

impl ToExpr for Span {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        mk_expr_struct(context, span, rt!["Span"], fields!(context, span, self, [lo, hi, expn_id]))
    }
}

// Token

impl ToExpr for BinOpToken {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        mk_expr_path(context, span, token!["BinOpToken", &format!("{:?}", self)])
    }
}

impl ToExpr for DelimToken {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        mk_expr_path(context, span, token!["DelimToken", &format!("{:?}", self)])
    }
}

impl ToExpr for token::Lit {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        use syntax::parse::token::Lit;
        let (variant, arguments) = match *self {
            Lit::Byte(name) => ("Byte", exprs!(context, span, [name])),
            Lit::Char(name) => ("Char", exprs!(context, span, [name])),
            Lit::Integer(name) => ("Integer", exprs!(context, span, [name])),
            Lit::Float(name) => ("Float", exprs!(context, span, [name])),
            Lit::Str_(name) => ("Str_", exprs!(context, span, [name])),
            Lit::StrRaw(name, size) => ("StrRaw", exprs!(context, span, [name, size])),
            Lit::ByteStr(name) => ("ByteStr", exprs!(context, span, [name])),
            Lit::ByteStrRaw(name, size) => ("ByteStrRaw", exprs!(context, span, [name, size])),
        };
        mk_expr_call(context, span, token!["Lit", variant], arguments)
    }
}

impl ToExpr for Token {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        let (variant, arguments) = match *self {
            Token::BinOp(binop) => ("BinOp", exprs!(context, span, [binop])),
            Token::BinOpEq(binop) => ("BinOpEq", exprs!(context, span, [binop])),
            Token::OpenDelim(delim) => ("OpenDelim", exprs!(context, span, [delim])),
            Token::CloseDelim(delim) => ("CloseDelim", exprs!(context, span, [delim])),
            Token::Literal(lit, suffix) => ("Literal",  exprs!(context, span, [lit, suffix])),
            Token::Ident(ref ident) => ("Ident",  exprs!(context, span, [ident])),
            Token::Lifetime(ref lifetime) => ("Lifetime",  exprs!(context, span, [lifetime])),
            Token::DocComment(comment) => ("DocComment",  exprs!(context, span, [comment])),
            Token::Interpolated(_) |
            Token::SubstNt(_) |
            Token::Shebang(_) => unreachable!("invalid quasiquoted token: {:?}", self),
            _ => return mk_expr_path(context, span, token!["Token", &format!("{:?}", self)]),
        };
        mk_expr_call(context, span, token!["Token", variant], arguments)
    }
}

// TokenTree

impl ToExpr for Delimited {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        let fields = fields!(context, span, self, [delim, tts]);
        mk_expr_struct(context, span, tokenstream!["Delimited"], fields)
    }
}

impl ToExpr for TokenTree {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        mk_expr_tt(context, span, self, span.to_expr(context, span))
    }
}

// AST

impl ToExpr for Ident {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        let arguments = vec![context.expr_str(span, self.name)];
        mk_expr_call(context, span, ast!["Ident", "from_str"], arguments)
    }
}

impl ToExpr for Symbol {
    fn to_expr(&self, context: &ExtCtxt, span: Span) -> P<Expr> {
        let arguments = vec![context.expr_str(span, *self)];
        mk_expr_call(context, span, symbol!["Symbol", "intern"], arguments)
    }
}

//================================================
// Functions
//================================================

pub fn mk_expr_call(context: &ExtCtxt, span: Span, idents: &[&str], args: Vec<P<Expr>>) -> P<Expr> {
    context.expr_call_global(span, mk_idents(context, idents), args)
}

pub fn mk_expr_method_call(
    context: &ExtCtxt, span: Span, expr: P<Expr>, ident: &str, args: Vec<P<Expr>>
) -> P<Expr> {
    context.expr_method_call(span, expr, context.ident_of(ident), args)
}

pub fn mk_expr_path(context: &ExtCtxt, span: Span, idents: &[&str]) -> P<Expr> {
    context.expr_path(context.path_global(span, mk_idents(context, idents)))
}

pub fn mk_expr_struct(
    context: &ExtCtxt, span: Span, idents: &[&str], fields: Vec<Field>
) -> P<Expr> {
    context.expr_struct(span, context.path_global(span, mk_idents(context, idents)), fields)
}

pub fn mk_expr_tt(context: &ExtCtxt, span: Span, tt: &TokenTree, ttspan: P<Expr>) -> P<Expr> {
    let (variant, argument) = match *tt {
        TokenTree::Token(_, ref token) => ("Token", token.to_expr(context, span)),
        TokenTree::Delimited(_, ref delimited) => ("Delimited", delimited.to_expr(context, span)),
    };
    mk_expr_call(context, span, tokenstream!["TokenTree", variant], vec![ttspan, argument])
}

pub fn mk_field(context: &ExtCtxt, span: Span, ident: &str, expr: P<Expr>) -> Field {
    context.field_imm(span, context.ident_of(ident), expr)
}

pub fn mk_idents(context: &ExtCtxt, idents: &[&str]) -> Vec<Ident> {
    idents.iter().map(|i| context.ident_of(i)).collect()
}