2016-07-03 16:55:45 +12:00
|
|
|
use syntax::ast::{Expr, Ident, Pat, Stmt};
|
2015-09-01 18:26:50 +12:00
|
|
|
use syntax::codemap::DUMMY_SP;
|
2014-12-18 18:57:55 +13:00
|
|
|
use syntax::ext::base::ExtCtxt;
|
|
|
|
use syntax::parse::token;
|
|
|
|
use syntax::ptr::P;
|
2016-07-03 16:55:45 +12:00
|
|
|
use syntax::tokenstream::TokenTree;
|
2014-12-18 18:57:55 +13:00
|
|
|
|
2015-09-12 18:05:58 +12:00
|
|
|
use maud::Escaper;
|
2015-01-11 11:01:39 +13:00
|
|
|
|
2016-06-12 15:05:49 +12:00
|
|
|
pub struct Renderer<'cx, 'a: 'cx> {
|
|
|
|
pub cx: &'cx ExtCtxt<'a>,
|
2015-09-01 18:56:02 +12:00
|
|
|
writer: Ident,
|
|
|
|
result: Ident,
|
2015-09-01 18:26:50 +12:00
|
|
|
loop_label: Vec<TokenTree>,
|
2016-02-14 20:12:04 +11:00
|
|
|
stmts: Vec<Stmt>,
|
2015-04-10 18:44:59 +12:00
|
|
|
tail: String,
|
2014-12-18 18:57:55 +13:00
|
|
|
}
|
|
|
|
|
2016-06-12 15:05:49 +12:00
|
|
|
impl<'cx, 'a> Renderer<'cx, 'a> {
|
2015-04-04 10:27:44 +13:00
|
|
|
/// Creates a new `Renderer` using the given extension context.
|
2016-06-12 15:05:49 +12:00
|
|
|
pub fn new(cx: &'cx ExtCtxt<'a>) -> Renderer<'cx, 'a> {
|
2015-09-01 18:56:02 +12:00
|
|
|
let writer = token::gensym_ident("__maud_writer");
|
|
|
|
let result = token::gensym_ident("__maud_result");
|
2016-06-12 15:39:12 +12:00
|
|
|
// Silence "duplicate loop labels" warning by appending ExpnId to label
|
|
|
|
// FIXME This is a gross hack and should be replaced ASAP
|
|
|
|
// See issues #36 and #37
|
|
|
|
let loop_label = token::gensym_ident(&format!("__maud_loop_label_{}", cx.backtrace.into_u32()));
|
2015-01-30 18:43:53 +13:00
|
|
|
Renderer {
|
|
|
|
cx: cx,
|
2015-09-01 18:56:02 +12:00
|
|
|
writer: writer,
|
|
|
|
result: result,
|
2015-11-08 18:59:11 +13:00
|
|
|
loop_label: vec![TokenTree::Token(DUMMY_SP, token::Lifetime(loop_label))],
|
2015-09-06 12:10:55 +12:00
|
|
|
stmts: vec![],
|
2015-04-10 18:44:59 +12:00
|
|
|
tail: String::new(),
|
2015-01-30 18:43:53 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-04 10:27:44 +13:00
|
|
|
/// Creates a new `Renderer` under the same context as `self`.
|
2016-06-12 15:05:49 +12:00
|
|
|
pub fn fork(&self) -> Renderer<'cx, 'a> {
|
2015-02-06 15:43:53 +13:00
|
|
|
Renderer {
|
|
|
|
cx: self.cx,
|
2015-09-01 18:56:02 +12:00
|
|
|
writer: self.writer,
|
|
|
|
result: self.result,
|
2015-09-01 18:26:50 +12:00
|
|
|
loop_label: self.loop_label.clone(),
|
2015-04-10 18:44:59 +12:00
|
|
|
stmts: Vec::new(),
|
|
|
|
tail: String::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Flushes the tail buffer, emitting a single `.write_str()` call.
|
|
|
|
fn flush(&mut self) {
|
|
|
|
if !self.tail.is_empty() {
|
|
|
|
let expr = {
|
2015-09-01 18:56:02 +12:00
|
|
|
let w = self.writer;
|
2015-04-10 18:44:59 +12:00
|
|
|
let s = &*self.tail;
|
|
|
|
quote_expr!(self.cx, $w.write_str($s))
|
|
|
|
};
|
2015-09-01 18:26:50 +12:00
|
|
|
let stmt = self.wrap_try(expr);
|
2015-04-10 18:44:59 +12:00
|
|
|
self.stmts.push(stmt);
|
|
|
|
self.tail.clear();
|
2015-02-06 15:43:53 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-08 22:51:21 +12:00
|
|
|
/// Reifies the `Renderer` into a block of markup.
|
2015-09-06 12:10:55 +12:00
|
|
|
pub fn into_expr(mut self, writer_expr: Vec<TokenTree>) -> P<Expr> {
|
|
|
|
let Renderer { cx, writer, result, loop_label, stmts, .. } = { self.flush(); self };
|
2015-09-01 18:26:50 +12:00
|
|
|
quote_expr!(cx, {
|
2015-09-01 18:56:02 +12:00
|
|
|
let mut $result = Ok(());
|
2015-09-01 18:26:50 +12:00
|
|
|
$loop_label: loop {
|
2015-09-25 15:03:00 +12:00
|
|
|
#[allow(unused_imports)]
|
2015-07-03 10:59:34 +12:00
|
|
|
use ::std::fmt::Write;
|
2015-09-06 12:10:55 +12:00
|
|
|
match &mut $writer_expr {
|
2015-09-07 11:47:25 +12:00
|
|
|
$writer => {
|
|
|
|
$writer as &mut ::std::fmt::Write;
|
|
|
|
$stmts
|
|
|
|
}
|
2015-09-06 12:10:55 +12:00
|
|
|
}
|
2015-09-01 18:26:50 +12:00
|
|
|
break $loop_label;
|
|
|
|
}
|
2015-09-01 18:56:02 +12:00
|
|
|
$result
|
2015-09-01 18:26:50 +12:00
|
|
|
})
|
2014-12-19 11:53:40 +13:00
|
|
|
}
|
|
|
|
|
2015-04-08 22:51:21 +12:00
|
|
|
/// Reifies the `Renderer` into a raw list of statements.
|
2016-02-14 20:12:04 +11:00
|
|
|
pub fn into_stmts(mut self) -> Vec<Stmt> {
|
2015-04-10 18:44:59 +12:00
|
|
|
let Renderer { stmts, .. } = { self.flush(); self };
|
2015-02-06 15:43:53 +13:00
|
|
|
stmts
|
2015-01-29 13:47:11 +13:00
|
|
|
}
|
|
|
|
|
2015-04-10 20:21:09 +12:00
|
|
|
/// Pushes a statement, flushing the tail buffer in the process.
|
2016-02-14 20:12:04 +11:00
|
|
|
fn push(&mut self, stmt: Stmt) {
|
2015-04-10 18:44:59 +12:00
|
|
|
self.flush();
|
2015-01-30 18:43:53 +13:00
|
|
|
self.stmts.push(stmt);
|
2015-01-17 20:43:51 +13:00
|
|
|
}
|
|
|
|
|
2015-04-10 18:44:59 +12:00
|
|
|
/// Pushes a literal string to the tail buffer.
|
|
|
|
fn push_str(&mut self, s: &str) {
|
|
|
|
self.tail.push_str(s);
|
2015-01-10 21:29:58 +13:00
|
|
|
}
|
|
|
|
|
2015-09-01 18:26:50 +12:00
|
|
|
/// Wraps an expression in a `try!` call.
|
2016-02-14 20:12:04 +11:00
|
|
|
fn wrap_try(&self, expr: P<Expr>) -> Stmt {
|
2015-09-01 18:56:02 +12:00
|
|
|
let result = self.result;
|
2015-09-01 18:26:50 +12:00
|
|
|
let loop_label = &self.loop_label;
|
|
|
|
quote_stmt!(
|
|
|
|
self.cx,
|
|
|
|
match $expr {
|
|
|
|
Ok(()) => {},
|
|
|
|
Err(e) => {
|
2015-09-01 18:56:02 +12:00
|
|
|
$result = Err(e);
|
2015-09-01 18:26:50 +12:00
|
|
|
break $loop_label;
|
|
|
|
}
|
|
|
|
}).unwrap()
|
|
|
|
}
|
|
|
|
|
2015-10-06 19:14:31 +13:00
|
|
|
/// Appends a literal string.
|
|
|
|
pub fn string(&mut self, s: &str) {
|
|
|
|
self.push_str(&html_escape(s));
|
2015-01-10 21:29:58 +13:00
|
|
|
}
|
|
|
|
|
2015-04-08 22:51:21 +12:00
|
|
|
/// Appends the result of an expression, with the specified escaping method.
|
2015-10-06 19:14:31 +13:00
|
|
|
pub fn splice(&mut self, expr: P<Expr>) {
|
2015-09-01 18:56:02 +12:00
|
|
|
let w = self.writer;
|
2016-02-02 15:22:45 +01:00
|
|
|
let expr = quote_expr!(self.cx, { use ::maud::RenderOnce; $expr.render_once(&mut *$w) });
|
2015-09-01 18:26:50 +12:00
|
|
|
let stmt = self.wrap_try(expr);
|
2015-04-10 18:44:59 +12:00
|
|
|
self.push(stmt);
|
2015-01-10 21:29:58 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn element_open_start(&mut self, name: &str) {
|
2015-04-10 18:44:59 +12:00
|
|
|
self.push_str("<");
|
|
|
|
self.push_str(name);
|
2015-01-10 21:29:58 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn attribute_start(&mut self, name: &str) {
|
2015-04-10 18:44:59 +12:00
|
|
|
self.push_str(" ");
|
|
|
|
self.push_str(name);
|
|
|
|
self.push_str("=\"");
|
2015-01-10 21:29:58 +13:00
|
|
|
}
|
|
|
|
|
2015-01-13 16:46:37 +13:00
|
|
|
pub fn attribute_empty(&mut self, name: &str) {
|
2015-04-10 18:44:59 +12:00
|
|
|
self.push_str(" ");
|
|
|
|
self.push_str(name);
|
2015-01-13 16:46:37 +13:00
|
|
|
}
|
|
|
|
|
2015-01-10 21:29:58 +13:00
|
|
|
pub fn attribute_end(&mut self) {
|
2015-04-10 18:44:59 +12:00
|
|
|
self.push_str("\"");
|
2015-01-10 21:29:58 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn element_open_end(&mut self) {
|
2015-04-10 18:44:59 +12:00
|
|
|
self.push_str(">");
|
2015-01-10 21:29:58 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn element_close(&mut self, name: &str) {
|
2015-04-10 18:44:59 +12:00
|
|
|
self.push_str("</");
|
|
|
|
self.push_str(name);
|
|
|
|
self.push_str(">");
|
2014-12-18 18:57:55 +13:00
|
|
|
}
|
2015-02-09 15:05:50 +13:00
|
|
|
|
2015-04-08 22:51:21 +12:00
|
|
|
/// Emits an `if` expression.
|
2015-03-15 16:23:19 +13:00
|
|
|
///
|
|
|
|
/// The condition is a token tree (not an expression) so we don't
|
|
|
|
/// need to special-case `if let`.
|
2016-02-14 20:12:04 +11:00
|
|
|
pub fn emit_if(&mut self, if_cond: Vec<TokenTree>, if_body: Vec<Stmt>,
|
|
|
|
else_body: Option<Vec<Stmt>>) {
|
2015-02-09 15:05:50 +13:00
|
|
|
let stmt = match else_body {
|
2015-03-01 19:34:08 -05:00
|
|
|
None => quote_stmt!(self.cx, if $if_cond { $if_body }),
|
2015-02-09 15:05:50 +13:00
|
|
|
Some(else_body) =>
|
2015-03-01 19:34:08 -05:00
|
|
|
quote_stmt!(self.cx, if $if_cond { $if_body } else { $else_body }),
|
2015-03-21 13:38:39 +13:00
|
|
|
}.unwrap();
|
2015-04-10 18:44:59 +12:00
|
|
|
self.push(stmt);
|
2015-02-09 15:05:50 +13:00
|
|
|
}
|
2015-03-14 21:08:08 +13:00
|
|
|
|
2016-02-14 20:12:04 +11:00
|
|
|
pub fn emit_for(&mut self, pattern: P<Pat>, iterable: P<Expr>, body: Vec<Stmt>) {
|
2015-03-21 13:38:39 +13:00
|
|
|
let stmt = quote_stmt!(self.cx, for $pattern in $iterable { $body }).unwrap();
|
2015-04-10 18:44:59 +12:00
|
|
|
self.push(stmt);
|
2015-03-14 21:08:08 +13:00
|
|
|
}
|
2015-09-23 14:29:45 +12:00
|
|
|
|
2016-02-01 20:05:50 +01:00
|
|
|
pub fn emit_match(&mut self, match_var: P<Expr>, match_body: Vec<TokenTree>) {
|
|
|
|
let stmt = quote_stmt!(self.cx, match $match_var { $match_body }).unwrap();
|
|
|
|
self.push(stmt);
|
|
|
|
}
|
|
|
|
|
2015-09-23 14:29:45 +12:00
|
|
|
pub fn emit_call(&mut self, func: P<Expr>) {
|
|
|
|
let w = self.writer;
|
|
|
|
let expr = quote_expr!(self.cx, ($func)(&mut *$w));
|
|
|
|
let stmt = self.wrap_try(expr);
|
|
|
|
self.push(stmt);
|
|
|
|
}
|
2014-12-18 18:57:55 +13:00
|
|
|
}
|
2015-09-12 18:05:58 +12:00
|
|
|
|
|
|
|
fn html_escape(s: &str) -> String {
|
|
|
|
use std::fmt::Write;
|
2015-09-16 11:45:57 +12:00
|
|
|
let mut escaper = Escaper::new(String::new());
|
|
|
|
escaper.write_str(s).unwrap();
|
|
|
|
escaper.into_inner()
|
2015-09-12 18:05:58 +12:00
|
|
|
}
|