sailfish/sailfish-compiler/src/resolver.rs

155 lines
4.1 KiB
Rust
Raw Normal View History

use quote::quote;
use std::path::{Path, PathBuf};
2020-06-06 09:49:01 -04:00
use std::sync::Arc;
use syn::visit_mut::VisitMut;
use syn::{Block, Expr, ExprBlock, LitStr};
2020-06-04 16:39:33 -04:00
use crate::error::*;
2020-06-06 09:49:01 -04:00
macro_rules! matches_or_else {
($val:expr, $p:pat, $ok:expr, $else:expr) => {
match $val {
$p => $ok,
_ => $else,
}
};
}
2020-06-04 16:39:33 -04:00
2020-06-06 09:49:01 -04:00
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<'s, 'h> {
template_dir: &'s Path,
path_stack: Vec<PathBuf>,
2020-06-06 09:49:01 -04:00
deps: Vec<String>,
error: Option<Error>,
include_handler: Arc<dyn 'h + Fn(&Path) -> Result<Block, Error>>,
2020-06-06 09:49:01 -04:00
}
impl<'s, 'h> VisitMut for ResolverImpl<'s, 'h> {
2020-06-06 09:49:01 -04:00
fn visit_expr_mut(&mut self, i: &mut Expr) {
return_if_some!(self.error);
let em = matches_or_else!(*i, Expr::Macro(ref mut em), em, {
2020-06-06 09:49:01 -04:00
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;
}
};
// resolve include! for rust file
2020-06-06 11:52:53 -04:00
if arg.ends_with(".rs") {
if !arg.starts_with("/") {
let absolute_path = self.path_stack.last().unwrap().parent().unwrap().join(arg);
let absolute_path_str = absolute_path.to_string_lossy();
em.mac.tokens = quote!{ include!(#absolute_path_str) };
}
2020-06-06 11:52:53 -04:00
return;
}
// resolve the template file path
2020-06-06 16:51:54 -04:00
// TODO: How should arguments be interpreted on Windows?
let input_file = if arg.starts_with('/') {
// absolute imclude
self.template_dir.join(&arg[1..])
} else {
// relative include
self.path_stack.last().unwrap().parent().unwrap().join(arg.clone())
};
2020-06-06 09:49:01 -04:00
// parse and translate the child template
let mut blk = match (*self.include_handler)(&*input_file) {
2020-06-06 09:49:01 -04:00
Ok(blk) => blk,
Err(mut e) => {
e.chains
.push(ErrorKind::Other(format!("Failed to include {}", arg)));
self.error = Some(e);
return;
}
};
self.path_stack.push(input_file);
2020-06-06 09:49:01 -04:00
self.deps.push(arg);
syn::visit_mut::visit_block_mut(self, &mut blk);
2020-06-06 14:21:17 -04:00
self.path_stack.pop();
2020-06-06 09:49:01 -04:00
*i = Expr::Block(ExprBlock {
attrs: Vec::new(),
label: None,
block: blk,
});
}
}
#[derive(Clone)]
pub struct Resolver<'h> {
include_handler: Arc<dyn 'h + Fn(&Path) -> Result<Block, Error>>,
2020-06-06 09:49:01 -04:00
}
impl<'h> Resolver<'h> {
2020-06-04 16:39:33 -04:00
pub fn new() -> Self {
2020-06-06 09:49:01 -04:00
Self {
include_handler: Arc::new(|_| {
Err(make_error!(ErrorKind::AnalyzeError(
"You cannot use `include` macro inside templates".to_owned()
)))
}),
}
}
#[inline]
pub fn include_handler(
self,
new: Arc<dyn 'h + Fn(&Path) -> Result<Block, Error>>,
2020-06-06 09:49:01 -04:00
) -> Resolver<'h> {
Self {
include_handler: new,
}
2020-06-04 16:39:33 -04:00
}
#[inline]
pub fn resolve(&self, template_dir: &Path, input_file: &Path, ast: &mut Block) -> Result<(), Error> {
let mut child = ResolverImpl {
template_dir: template_dir,
path_stack: vec![input_file.to_owned()],
2020-06-06 09:49:01 -04:00
deps: Vec::new(),
error: None,
include_handler: Arc::clone(&self.include_handler)
};
child.visit_block_mut(ast);
if let Some(e) = child.error {
Err(e)
} else {
Ok(())
}
2020-06-04 16:39:33 -04:00
}
}