use rust;
use rust::Token as rtok;
use rust::{TokenAndSpan, Span, token_to_string};
use compiler::*;
use std::collections::hash_map::HashMap;
pub struct Quasiquote<'a, 'b:'a, 'c, C> where C: 'c
{
cx: &'a rust::ExtCtxt<'b>,
compiler: &'c mut C,
tokens: Vec<TokenAndSpan>,
current_idx: usize,
unquoted_tokens: Vec<TokenAndSpan>
}
impl<'a, 'b, 'c, C> Quasiquote<'a, 'b, 'c, C> where
C: 'c + Compiler
{
pub fn compile(cx: &'a rust::ExtCtxt<'b>, tokens: Vec<TokenAndSpan>, compiler: &'c mut C)
-> Vec<TokenAndSpan>
{
let mut quasiquote = Quasiquote::new(cx, compiler, tokens);
quasiquote.unquote_all();
quasiquote.unquoted_tokens
}
fn new(cx: &'a rust::ExtCtxt<'b>, compiler: &'c mut C, tokens: Vec<TokenAndSpan>)
-> Quasiquote<'a, 'b, 'c, C>
{
Quasiquote {
cx: cx,
compiler: compiler,
tokens: tokens,
current_idx: 0,
unquoted_tokens: vec![]
}
}
fn unquote_all(&mut self) {
while !self.at_end() {
match self.peek_unquote() {
None => {
let tok = self.token();
self.unquoted_tokens.push(tok);
}
Some(d) => {
self.unquote(d);
}
}
self.bump(1);
}
}
fn bump(&mut self, n: usize) {
self.current_idx = self.current_idx + n;
}
fn at_end(&self) -> bool {
self.current_idx >= self.tokens.len()
}
fn token(&self) -> TokenAndSpan {
self.tokens[self.current_idx].clone()
}
fn peek_unquote(&self) -> Option<rust::DelimToken> {
if self.current_idx + 1 < self.tokens.len()
&& self.tokens[self.current_idx].tok == rtok::Pound
{
match self.tokens[self.current_idx + 1].tok {
rtok::OpenDelim(d@rust::DelimToken::Paren) |
rtok::OpenDelim(d@rust::DelimToken::Brace) => Some(d),
_ => None
}
}
else {
None
}
}
fn unquote(&mut self, delim: rust::DelimToken) {
let pound_idx = self.current_idx;
let mut opened_delims = 1isize;
self.bump(2);
while !self.at_end()
&& self.still_in_quote(delim, opened_delims)
{
opened_delims = opened_delims + self.count_delim(delim);
self.bump(1);
}
if self.at_end() || opened_delims != 1 {
self.cx.span_fatal(self.tokens[pound_idx + 1].sp,
"unclosed delimiter of anynomous macro.");
}
let unquote = self.make_unquote(pound_idx);
self.compile_unquote(delim, unquote);
}
fn still_in_quote(&self, delim: rust::DelimToken,
opened_delims: isize) -> bool
{
opened_delims != 1
|| self.token().tok != rtok::CloseDelim(delim)
}
fn count_delim(&self, delim: rust::DelimToken) -> isize {
match self.token().tok {
rtok::CloseDelim(d) if d == delim => -1,
rtok::OpenDelim(d) if d == delim => 1,
_ => 0
}
}
fn compile_unquote(&mut self, delim: rust::DelimToken, unquote: Unquote) {
let span = unquote.span;
let non_terminal =
match delim {
rust::DelimToken::Paren => {
rust::Nonterminal::NtExpr(self.compiler.compile_expr(unquote))
}
rust::DelimToken::Brace => {
rust::Nonterminal::NtBlock(self.compiler.compile_block(unquote))
}
d => panic!("compile_unquote: unrecognized delimiter {:?}", d)
};
let interpolated_tok = rtok::Interpolated(non_terminal);
let unquoted_tok = TokenAndSpan {
tok: interpolated_tok,
sp: span
};
self.unquoted_tokens.push(unquoted_tok);
}
fn make_unquote(&self, start_idx: usize) -> Unquote {
let mut code = String::new();
let mut text_to_ident = HashMap::new();
for idx in (start_idx+2)..self.current_idx {
if let rtok::Ident(id) = self.tokens[idx].tok {
text_to_ident.insert(format!("{}", id), id);
}
code.extend(token_to_string(&self.tokens[idx].tok).chars());
code.push(' ');
}
Unquote {
text_to_ident: text_to_ident,
code: code,
span: self.span_from(start_idx)
}
}
fn span_from(&self, start_idx: usize) -> Span {
let mut span = self.tokens[start_idx].sp;
span.hi = self.token().sp.hi;
span
}
}