use proc_macro2::Span; use crate::error::*; use crate::parser::{ParseStream, Token, TokenKind}; use syn::Block; #[derive(Clone)] pub struct SourceMapEntry { pub original: usize, pub new: usize, pub length: usize, } #[derive(Default)] pub struct SourceMap { entries: Vec, } impl SourceMap { #[inline] pub fn entries(&self) -> &[SourceMapEntry] { &*self.entries } pub fn reverse_mapping(&self, offset: usize) -> Option { // find entry which satisfies entry.new <= offset < entry.new + entry.length let idx = self .entries .iter() .position(|entry| offset < entry.new + entry.length && entry.new <= offset)?; let entry = &self.entries[idx]; debug_assert!(entry.new <= offset); debug_assert!(offset < entry.new + entry.length); Some(entry.original + offset - entry.new) } } pub struct TranslatedSource { pub ast: Block, pub source_map: SourceMap, } // translate tokens into Rust code #[derive(Clone, Debug, Default)] pub struct Translator { escape: bool, } impl Translator { #[inline] pub fn new() -> Self { Self { escape: true } } #[inline] pub fn escape(mut self, new: bool) -> Self { self.escape = new; self } pub fn translate<'a>( &self, token_iter: ParseStream<'a>, ) -> Result { let original_source = token_iter.original_source; let mut source = String::with_capacity(original_source.len()); source.push_str("{\n"); let mut ps = SourceBuilder { escape: self.escape, source, source_map: SourceMap::default(), }; ps.feed_tokens(&*token_iter.into_vec()?); Ok(ps.finalize()?) } } struct SourceBuilder { escape: bool, source: String, source_map: SourceMap, } impl SourceBuilder { fn write_token<'a>(&mut self, token: &Token<'a>) { let entry = SourceMapEntry { original: token.offset(), new: self.source.len(), length: token.as_str().len(), }; self.source_map.entries.push(entry); self.source.push_str(token.as_str()); } fn write_code<'a>(&mut self, token: &Token<'a>) { // TODO: automatically add missing tokens (e.g. ';', '{') self.write_token(token); self.source.push_str("\n"); } fn write_text<'a>(&mut self, token: &Token<'a>) { use std::fmt::Write; self.source.push_str("sfrt::render_text!(_ctx, "); // write text token with Debug::fmt write!(self.source, "{:?}", token.as_str()).unwrap(); self.source.push_str(");\n"); } fn write_buffered_code<'a>(&mut self, token: &Token<'a>, escape: bool) { let method = if self.escape && escape { "render_escaped" } else { "render" }; self.source.push_str("sfrt::"); self.source.push_str(method); self.source.push_str("!(_ctx, "); self.write_token(token); self.source.push_str(");\n"); } pub fn feed_tokens(&mut self, token_iter: &[Token]) { let mut it = token_iter.iter().peekable(); while let Some(token) = it.next() { match token.kind() { TokenKind::Code => self.write_code(&token), TokenKind::Comment => {} TokenKind::BufferedCode { escape } => { self.write_buffered_code(&token, escape) } TokenKind::Text => { // concatenate repeated text token let offset = token.offset(); let mut concatenated = String::new(); concatenated.push_str(token.as_str()); while let Some(next_token) = it.peek() { match next_token.kind() { TokenKind::Text => { concatenated.push_str(next_token.as_str()); it.next(); } TokenKind::Comment => { it.next(); } _ => break, } } let new_token = Token::new(&*concatenated, offset, TokenKind::Text); self.write_text(&new_token); } } } } pub fn finalize(mut self) -> Result { self.source.push_str("\n}"); match syn::parse_str::(&*self.source) { Ok(ast) => Ok(TranslatedSource { ast, source_map: self.source_map, }), Err(synerr) => { let span = synerr.span(); let original_offset = into_offset(&*self.source, span) .and_then(|o| self.source_map.reverse_mapping(o)); let mut err = make_error!(ErrorKind::RustSyntaxError(synerr), source = self.source); err.offset = original_offset; Err(err) } } } } fn into_offset(source: &str, span: Span) -> Option { let lc = span.start(); if lc.line > 0 { Some( source .lines() .take(lc.line - 1) .fold(0, |s, e| s + e.len() + 1) + lc.column, ) } else { None } } #[cfg(test)] mod tests { use super::*; use crate::parser::Parser; #[test] fn translate() { let src = "<% pub fn sample() { %> <%% <%=//%>\n%><% } %>"; let lexer = Parser::new(); let token_iter = lexer.parse(src); let mut ps = SourceBuilder { escape: true, source: String::with_capacity(token_iter.original_source.len()), source_map: SourceMap::default(), }; ps.feed_tokens(&token_iter.clone().to_vec().unwrap()); eprintln!("{}", ps.source); Translator::new().translate(token_iter).unwrap(); } }