use std::any::Any;
use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::fs::File;
use std::io;
use std::io::Read;
use std::mem;
use std::path::PathBuf;
use std::sync::{Mutex, RwLock};
use futures::sync::oneshot;
use futures::{future, Future};
use itertools::Itertools;
use crate::base::ast::{expr_to_path, Expr, Literal, SpannedExpr, Typed, TypedIdent};
use crate::base::error::{Errors, InFile};
use crate::base::filename_to_module;
use crate::base::fnv::FnvMap;
use crate::base::pos::{self, BytePos, Span};
use crate::base::symbol::Symbol;
use crate::base::types::{ArcType, Type};
use crate::vm::{
self,
macros::{Error as MacroError, Macro, MacroExpander, MacroFuture},
thread::{Thread, ThreadInternal},
ExternLoader, ExternModule,
};
use super::Compiler;
quick_error! {
#[derive(Debug)]
pub enum Error {
CyclicDependency(module: String, cycle: Vec<String>) {
description("Cyclic dependency")
display(
"Module '{}' occurs in a cyclic dependency: `{}`",
module,
cycle.iter().chain(Some(module)).format(" -> ")
)
}
String(message: String) {
description(message)
display("{}", message)
}
IO(err: io::Error) {
description(err.description())
display("{}", err)
from()
}
}
}
pub const COMPILER_KEY: &str = "Compiler";
include!(concat!(env!("OUT_DIR"), "/std_modules.rs"));
pub trait Importer: Any + Clone + Sync + Send {
fn import(
&self,
compiler: &mut Compiler,
vm: &Thread,
earlier_errors_exist: bool,
modulename: &str,
input: &str,
expr: SpannedExpr<Symbol>,
) -> Result<(), (Option<ArcType>, MacroError)>;
}
#[derive(Clone)]
pub struct DefaultImporter;
impl Importer for DefaultImporter {
fn import(
&self,
compiler: &mut Compiler,
vm: &Thread,
earlier_errors_exist: bool,
modulename: &str,
input: &str,
mut expr: SpannedExpr<Symbol>,
) -> Result<(), (Option<ArcType>, MacroError)> {
use crate::compiler_pipeline::*;
let result = {
let result = MacroValue { expr: &mut expr }
.typecheck(compiler, vm, modulename, input)
.map_err(|err| err.into());
if result.is_ok() && earlier_errors_exist {
trace!(
"Typechecked {} but earlier errors exist, bailing",
modulename
);
return Err((
Some(expr.env_type_of(&*vm.get_env())),
Box::new(crate::Error::Multiple(Errors::default())),
));
}
result.and_then(|value| {
value
.load_script(compiler, vm, modulename, input, ())
.wait()
})
};
result.map_err(|err| (Some(expr.env_type_of(&*vm.get_env())), err.into()))
}
}
enum UnloadedModule {
Source(Cow<'static, str>),
Extern(ExternModule),
}
pub struct Import<I = DefaultImporter> {
pub paths: RwLock<Vec<PathBuf>>,
pub loaders: RwLock<FnvMap<String, ExternLoader>>,
pub importer: I,
loading: Mutex<FnvMap<String, future::Shared<oneshot::Receiver<()>>>>,
}
impl<I> Import<I> {
pub fn new(importer: I) -> Import<I> {
Import {
paths: RwLock::new(vec![PathBuf::from(".")]),
loaders: RwLock::default(),
importer: importer,
loading: Mutex::default(),
}
}
pub fn add_path<P: Into<PathBuf>>(&self, path: P) {
self.paths.write().unwrap().push(path.into());
}
pub fn set_paths(&self, paths: Vec<PathBuf>) {
*self.paths.write().unwrap() = paths;
}
pub fn add_loader(&self, module: &str, loader: ExternLoader) {
self.loaders
.write()
.unwrap()
.insert(String::from(module), loader);
}
pub fn modules(&self) -> Vec<Cow<'static, str>> {
STD_LIBS
.iter()
.map(|t| Cow::Borrowed(t.0))
.chain(self.loaders.read().unwrap().keys().cloned().map(Cow::Owned))
.collect()
}
fn get_unloaded_module(
&self,
vm: &Thread,
module: &str,
filename: &str,
) -> Result<UnloadedModule, MacroError> {
let mut buffer = String::new();
let std_file = STD_LIBS.iter().find(|tup| tup.0 == module);
if let Some(tup) = std_file {
return Ok(UnloadedModule::Source(Cow::Borrowed(tup.1)));
}
Ok(match std_file {
Some(tup) => UnloadedModule::Source(Cow::Borrowed(tup.1)),
None => {
{
let mut loaders = self.loaders.write().unwrap();
if let Some(loader) = loaders.get_mut(module) {
let value = loader(vm)?;
return Ok(UnloadedModule::Extern(value));
}
}
let paths = self.paths.read().unwrap();
let file = paths
.iter()
.filter_map(|p| {
let base = p.join(filename);
match File::open(&base) {
Ok(file) => Some(file),
Err(_) => None,
}
})
.next();
let mut file = file.ok_or_else(|| {
Error::String(format!(
"Could not find module '{}'. Searched {}.",
module,
paths
.iter()
.map(|p| format!("`{}`", p.display()))
.format(", ")
))
})?;
file.read_to_string(&mut buffer)?;
UnloadedModule::Source(Cow::Owned(buffer))
}
})
}
pub fn load_module(
&self,
compiler: &mut Compiler,
vm: &Thread,
macros: &mut MacroExpander,
module_id: &Symbol,
span: Span<BytePos>,
) -> Result<Option<impl Future<Item = (), Error = ()>>, (Option<ArcType>, MacroError)>
where
I: Importer,
{
assert!(module_id.is_global());
let modulename = module_id.name().definition_name();
let mut filename = modulename.replace(".", "/");
filename.push_str(".glu");
{
let state = get_state(macros);
if state.visited.iter().any(|m| **m == *filename) {
let cycle = state
.visited
.iter()
.skip_while(|m| **m != *filename)
.cloned()
.collect();
return Err((
None,
Error::CyclicDependency(filename.clone(), cycle).into(),
));
}
state.visited.push(filename.clone());
}
let sender = {
let mut loading = self.loading.lock().unwrap();
match loading.entry(module_id.to_string()) {
Entry::Occupied(entry) => {
get_state(macros).visited.pop();
return Ok(Some(entry.get().clone().map(|_| ()).map_err(|_| ())));
}
Entry::Vacant(entry) => {
let (sender, receiver) = oneshot::channel();
entry.insert(receiver.shared());
sender
}
}
};
if vm.global_env().global_exists(module_id.definition_name()) {
let _ = sender.send(());
get_state(macros).visited.pop();
return Ok(None);
}
let result = self.load_module_(compiler, vm, macros, module_id, &filename, span);
if let Err((ref typ, ref err)) = result {
debug!("Import error {}: {}", module_id, err);
if let Some(typ) = typ {
debug!("Import got type {}", typ);
}
}
let _ = sender.send(());
get_state(macros).visited.pop();
self.loading.lock().unwrap().remove(module_id.as_ref());
result.map(|_| None)
}
fn load_module_(
&self,
compiler: &mut Compiler,
vm: &Thread,
macros: &mut MacroExpander,
module_id: &Symbol,
filename: &str,
span: Span<BytePos>,
) -> Result<(), (Option<ArcType>, MacroError)>
where
I: Importer,
{
use crate::compiler_pipeline::*;
let modulename = module_id.name().definition_name();
let unloaded_module = self
.get_unloaded_module(vm, &modulename, &filename)
.map_err(|err| (None, err.into()))?;
match unloaded_module {
UnloadedModule::Extern(ExternModule {
value,
typ,
metadata,
}) => {
vm.set_global(module_id.clone(), typ, metadata, value.get_value())
.map_err(|err| (None, err.into()))?;
}
UnloadedModule::Source(file_contents) => {
let implicit_prelude = !file_contents.starts_with("//@NO-IMPLICIT-PRELUDE");
compiler.set_implicit_prelude(implicit_prelude);
let prev_errors = mem::replace(&mut macros.errors, Errors::new());
let result =
file_contents.expand_macro_with(compiler, macros, &modulename, &file_contents);
let has_errors =
macros.errors.has_errors() || result.is_err() || macros.error_in_expr;
let errors = mem::replace(&mut macros.errors, prev_errors);
if errors.has_errors() {
macros.errors.push(pos::spanned(
span,
Box::new(crate::Error::Macro(InFile::new(
compiler.code_map().clone(),
errors,
))),
));
}
let macro_result = match result {
Ok(m) => m,
Err((None, err)) => {
return Err((None, err.into()));
}
Err((Some(m), err)) => {
macros.errors.push(pos::spanned(span, err.into()));
m
}
};
self.importer.import(
compiler,
vm,
has_errors,
&modulename,
&file_contents,
macro_result.expr,
)?;
}
}
Ok(())
}
}
pub fn add_extern_module<F>(thread: &Thread, name: &str, loader: F)
where
F: FnMut(&Thread) -> vm::Result<ExternModule> + Send + Sync + 'static,
{
add_extern_module_(thread, name, Box::new(loader))
}
fn add_extern_module_(thread: &Thread, name: &str, loader: ExternLoader) {
let opt_macro = thread.get_macros().get("import");
let import = opt_macro
.as_ref()
.and_then(|mac| mac.downcast_ref::<Import>())
.unwrap_or_else(|| {
ice!(
"Can't add an extern module with a import macro. \
Did you mean to create this `Thread` with `gluon::new_vm`"
)
});
import.add_loader(name, loader);
}
macro_rules! add_extern_module_if {
(
#[cfg($($features: tt)*)],
available_if = $msg: expr,
args($vm: expr, $mod_name: expr, $loader: path)
) => {{
#[cfg($($features)*)]
$crate::import::add_extern_module($vm, $mod_name, $loader);
#[cfg(not($($features)*))]
$crate::import::add_extern_module($vm, $mod_name, |_: &::vm::thread::Thread| -> ::vm::Result<::vm::ExternModule> {
Err(::vm::Error::Message(
format!(
"{} is only available if {}",
$mod_name,
$msg
)
))
});
}};
}
fn get_state<'m>(macros: &'m mut MacroExpander) -> &'m mut State {
macros
.state
.entry(String::from("import"))
.or_insert_with(|| {
Box::new(State {
visited: Vec::new(),
modules_with_errors: FnvMap::default(),
})
})
.downcast_mut::<State>()
.unwrap()
}
struct State {
visited: Vec<String>,
modules_with_errors: FnvMap<String, Expr<Symbol>>,
}
impl<I> Macro for Import<I>
where
I: Importer,
{
fn expand(&self, macros: &mut MacroExpander, args: Vec<SpannedExpr<Symbol>>) -> MacroFuture {
fn get_module_name(args: &[SpannedExpr<Symbol>]) -> Result<String, MacroError> {
if args.len() != 1 {
return Err(Error::String("Expected import to get 1 argument".into()).into());
}
let modulename = match args[0].value {
Expr::Ident(_) | Expr::Projection(..) => {
let mut modulename = String::new();
expr_to_path(&args[0], &mut modulename)
.map_err(|err| Error::String(err.to_string()))?;
modulename
}
Expr::Literal(Literal::String(ref filename)) => {
format!("@{}", filename_to_module(filename))
}
_ => {
return Err(
Error::String("Expected a string literal or path to import".into()).into(),
);
}
};
Ok(modulename)
}
let modulename = match get_module_name(&args) {
Ok(modulename) => modulename,
Err(err) => return Box::new(future::err(err)),
};
info!("import! {}", modulename);
let vm = macros.vm;
let name = Symbol::from(if modulename.starts_with('@') {
modulename.clone()
} else {
format!("@{}", modulename)
});
debug!("Import '{}' {:?}", modulename, get_state(macros).visited);
if !vm.global_env().global_exists(&modulename) {
if let Some(expr) = get_state(macros)
.modules_with_errors
.get(&modulename)
.cloned()
{
macros.error_in_expr = true;
trace!("Marking error due to {} import", modulename);
return Box::new(future::ok(pos::spanned(args[0].span, expr)));
}
let mut compiler = macros
.state
.get(COMPILER_KEY)
.and_then(|any| any.downcast_ref::<Compiler>())
.expect("No `Compiler` in the macro state")
.split();
match self.load_module(&mut compiler, vm, macros, &name, args[0].span) {
Ok(Some(future)) => {
let span = args[0].span;
return Box::new(
future
.map_err(|_| unreachable!())
.map(move |_| pos::spanned(span, Expr::Ident(TypedIdent::new(name)))),
);
}
Ok(None) => (),
Err((typ, err)) => {
macros.errors.push(pos::spanned(args[0].span, err));
trace!(
"Marking error for {}: {}",
modulename,
typ.clone().unwrap_or_else(Type::hole)
);
let expr = Expr::Error(typ);
get_state(macros)
.modules_with_errors
.insert(modulename, expr.clone());
return Box::new(future::ok(pos::spanned(args[0].span, expr)));
}
}
}
Box::new(future::ok(pos::spanned(
args[0].span,
Expr::Ident(TypedIdent::new(name)),
)))
}
}