diff --git a/sailfish-compiler/src/compiler.rs b/sailfish-compiler/src/compiler.rs index 09b2e55..ef1ade1 100644 --- a/sailfish-compiler/src/compiler.rs +++ b/sailfish-compiler/src/compiler.rs @@ -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 { 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); diff --git a/sailfish-compiler/src/procmacro.rs b/sailfish-compiler/src/procmacro.rs index 1f4b1b3..4370e5c 100644 --- a/sailfish-compiler/src/procmacro.rs +++ b/sailfish-compiler/src/procmacro.rs @@ -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 { @@ -138,15 +139,13 @@ fn derive_template_impl(tokens: TokenStream) -> Result 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 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(); diff --git a/sailfish-compiler/src/resolver.rs b/sailfish-compiler/src/resolver.rs index b30c4b7..c89b9e7 100644 --- a/sailfish-compiler/src/resolver.rs +++ b/sailfish-compiler/src/resolver.rs @@ -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, + error: Option, + include_handler: Arc Result>, +} + +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::(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 Result>, +} + +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 Result>, + ) -> 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(()) } }