Implement include! macro

This commit is contained in:
Kogia-sima 2020-06-06 22:49:01 +09:00
parent 188e90cfc3
commit 12174ff5c5
3 changed files with 151 additions and 29 deletions

View File

@ -1,13 +1,14 @@
use quote::ToTokens;
use std::fs;
use std::path::Path;
use std::sync::Arc;
use crate::config::Config;
use crate::error::*;
use crate::optimizer::Optimizer;
use crate::parser::Parser;
use crate::resolver::Resolver;
use crate::translator::Translator;
use crate::translator::{Translator, TranslatedSource};
use crate::util::rustfmt_block;
#[derive(Default)]
@ -24,21 +25,35 @@ impl Compiler {
Self { config }
}
pub fn compile_file(&self, input: &Path, output: &Path) -> Result<(), Error> {
// TODO: introduce cache system
fn translate_file_contents(&self, input: &Path) -> Result<TranslatedSource, Error> {
let parser = Parser::new().delimiter(self.config.delimiter);
let translator = Translator::new().escape(self.config.escape);
let resolver = Resolver::new();
let content = fs::read_to_string(input)
.chain_err(|| format!("Failed to open template file: {:?}", input))?;
let stream = parser.parse(&*content);
translator.translate(stream)
}
pub fn compile_file(&self, template_dir: &Path, input: &Path, output: &Path) -> Result<(), Error> {
// TODO: introduce cache system
let input = if input.is_absolute() {
input.to_owned()
} else {
template_dir.join(input)
};
let include_handler = Arc::new(|arg: &str| -> Result<_, Error> {
let input_file = template_dir.join(arg);
Ok(self.translate_file_contents(&*input_file)?.ast)
});
let resolver = Resolver::new().include_handler(include_handler);
let optimizer = Optimizer::new();
let compile_file = |input: &Path, output: &Path| -> Result<(), Error> {
let content = fs::read_to_string(&*input)
.chain_err(|| format!("Failed to open template file: {:?}", input))?;
let stream = parser.parse(&*content);
let mut tsource = translator.translate(stream)?;
drop(content);
let mut tsource = self.translate_file_contents(input)?;
resolver.resolve(&mut tsource.ast)?;
optimizer.optimize(&mut tsource.ast);

View File

@ -113,6 +113,7 @@ struct TemplateStruct {
}
fn compile(
template_dir: &Path,
input_file: &Path,
output_file: &Path,
options: &DeriveTemplateOptions,
@ -126,7 +127,7 @@ fn compile(
}
let compiler = Compiler::with_config(config);
compiler.compile_file(input_file, &*output_file)
compiler.compile_file(template_dir, input_file, &*output_file)
}
fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error> {
@ -138,15 +139,13 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
all_options.merge(opt)?;
}
let mut template_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect(
"Internal error: environmental variable `CARGO_MANIFEST_DIR` is not set.",
));
template_dir.push("templates");
let input_file = match all_options.path {
Some(ref path) => {
let mut input = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect(
"Internal error: environmental variable `CARGO_MANIFEST_DIR` is not set.",
));
input.push("templates");
input.push(path.value());
input
}
Some(ref path) => template_dir.join(path.value()),
None => {
return Err(syn::Error::new(
Span::call_site(),
@ -178,7 +177,7 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
output_file.push("templates");
output_file.push(filename);
compile(&*input_file, &*output_file, &all_options)
compile(&*template_dir, &*input_file, &*output_file, &all_options)
.map_err(|e| syn::Error::new(Span::call_site(), e))?;
let input_file_string = input_file.to_string_lossy();

View File

@ -1,19 +1,127 @@
use syn::Block;
use std::sync::Arc;
use syn::visit_mut::VisitMut;
use syn::{Block, Expr, ExprBlock, LitStr};
use crate::error::*;
#[derive(Clone, Debug, Default)]
pub struct Resolver {}
macro_rules! matches_or_else {
($val:expr, $p:pat, $ok:expr, $else:expr) => {
match $val {
$p => $ok,
_ => $else,
}
};
}
impl Resolver {
#[inline]
macro_rules! return_if_some {
($val:expr) => {
if $val.is_some() {
return;
}
};
}
fn empty_block() -> Block {
Block {
brace_token: Default::default(),
stmts: Vec::new(),
}
}
struct ResolverImpl<'h> {
deps: Vec<String>,
error: Option<Error>,
include_handler: Arc<dyn 'h + Fn(&str) -> Result<Block, Error>>,
}
impl<'h> VisitMut for ResolverImpl<'h> {
fn visit_expr_mut(&mut self, i: &mut Expr) {
return_if_some!(self.error);
let em = matches_or_else!(*i, Expr::Macro(ref em), em, {
syn::visit_mut::visit_expr_mut(self, i);
return;
});
// check if path is `include`
if !em.mac.path.is_ident("include") {
syn::visit_mut::visit_expr_mut(self, i);
return;
}
let arg = match syn::parse2::<LitStr>(em.mac.tokens.clone()) {
Ok(l) => l.value(),
Err(e) => {
let mut e = Error::from(e);
e.chains.push(ErrorKind::AnalyzeError(
"invalid arguments for `include` macro".to_owned(),
));
self.error = Some(e);
return;
}
};
// TODO: support relative path
if arg.starts_with("./") || arg.starts_with("../") {
self.error = Some(make_error!(ErrorKind::Unimplemented(
"include! with relative path is not supported yet.".to_owned()
)))
}
// parse and translate the child template
let mut blk = match (*self.include_handler)(&arg) {
Ok(blk) => blk,
Err(mut e) => {
e.chains
.push(ErrorKind::Other(format!("Failed to include {}", arg)));
self.error = Some(e);
return;
}
};
self.deps.push(arg);
syn::visit_mut::visit_block_mut(self, &mut blk);
*i = Expr::Block(ExprBlock {
attrs: Vec::new(),
label: None,
block: blk,
});
}
}
#[derive(Clone)]
pub struct Resolver<'h> {
include_handler: Arc<dyn 'h + Fn(&str) -> Result<Block, Error>>,
}
impl<'h> Resolver<'h> {
pub fn new() -> Self {
Self {}
Self {
include_handler: Arc::new(|_| {
Err(make_error!(ErrorKind::AnalyzeError(
"You cannot use `include` macro inside templates".to_owned()
)))
}),
}
}
#[inline]
pub fn resolve(&self, _ast: &mut Block) -> Result<(), Error> {
// not implemented yet
pub fn include_handler(
self,
new: Arc<dyn 'h + Fn(&str) -> Result<Block, Error>>,
) -> Resolver<'h> {
Self {
include_handler: new,
}
}
#[inline]
pub fn resolve(&self, ast: &mut Block) -> Result<(), Error> {
ResolverImpl {
deps: Vec::new(),
error: None,
include_handler: Arc::clone(&self.include_handler)
}.visit_block_mut(ast);
Ok(())
}
}