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

View File

@ -113,6 +113,7 @@ struct TemplateStruct {
} }
fn compile( fn compile(
template_dir: &Path,
input_file: &Path, input_file: &Path,
output_file: &Path, output_file: &Path,
options: &DeriveTemplateOptions, options: &DeriveTemplateOptions,
@ -126,7 +127,7 @@ fn compile(
} }
let compiler = Compiler::with_config(config); 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> { 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)?; 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 { let input_file = match all_options.path {
Some(ref path) => { Some(ref path) => template_dir.join(path.value()),
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
}
None => { None => {
return Err(syn::Error::new( return Err(syn::Error::new(
Span::call_site(), Span::call_site(),
@ -178,7 +177,7 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
output_file.push("templates"); output_file.push("templates");
output_file.push(filename); 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))?; .map_err(|e| syn::Error::new(Span::call_site(), e))?;
let input_file_string = input_file.to_string_lossy(); 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::*; use crate::error::*;
#[derive(Clone, Debug, Default)] macro_rules! matches_or_else {
pub struct Resolver {} ($val:expr, $p:pat, $ok:expr, $else:expr) => {
match $val {
$p => $ok,
_ => $else,
}
};
}
impl Resolver { macro_rules! return_if_some {
#[inline] ($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 { 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] #[inline]
pub fn resolve(&self, _ast: &mut Block) -> Result<(), Error> { pub fn include_handler(
// not implemented yet 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(()) Ok(())
} }
} }