224 lines
6.1 KiB
Rust
224 lines
6.1 KiB
Rust
|
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<SourceMapEntry>,
|
||
|
}
|
||
|
|
||
|
impl SourceMap {
|
||
|
#[inline]
|
||
|
pub fn entries(&self) -> &[SourceMapEntry] {
|
||
|
&*self.entries
|
||
|
}
|
||
|
|
||
|
pub fn reverse_mapping(&self, offset: usize) -> Option<usize> {
|
||
|
// 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<TranslatedSource, Error> {
|
||
|
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<TranslatedSource, Error> {
|
||
|
self.source.push_str("\n}");
|
||
|
match syn::parse_str::<Block>(&*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<usize> {
|
||
|
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();
|
||
|
}
|
||
|
}
|