use std::{
any::{Any, TypeId},
borrow::Cow,
fs::File,
io::Read,
mem,
ops::{Deref, DerefMut},
path::PathBuf,
sync::{Arc, Mutex, MutexGuard, RwLock},
};
use futures::future;
use itertools::Itertools;
use salsa::ParallelDatabase;
use crate::base::{
ast::{expr_to_path, Expr, Literal, SpannedExpr},
filename_to_module,
fnv::FnvMap,
pos,
symbol::Symbol,
types::ArcType,
};
use crate::vm::{
self,
gc::Trace,
macros::{Error as MacroError, Macro, MacroExpander, MacroFuture},
thread::{RootedThread, Thread, ThreadInternal},
vm::VmEnv,
ExternLoader, ExternModule,
};
use crate::{
query::{Compilation, CompilerDatabase},
IoError, ModuleCompiler,
};
quick_error! {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
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: IoError) {
description(err.description())
display("{}", err)
from()
}
}
}
impl base::error::AsDiagnostic for Error {
fn as_diagnostic(&self) -> codespan_reporting::Diagnostic {
codespan_reporting::Diagnostic::new_error(self.to_string())
}
}
include!(concat!(env!("OUT_DIR"), "/std_modules.rs"));
pub trait Importer: Any + Clone + Sync + Send {
fn import(
&self,
compiler: &mut ModuleCompiler,
vm: &Thread,
modulename: &str,
) -> Result<ArcType, (Option<ArcType>, crate::Error)>;
}
#[derive(Clone)]
pub struct DefaultImporter;
impl Importer for DefaultImporter {
fn import(
&self,
compiler: &mut ModuleCompiler,
_vm: &Thread,
modulename: &str,
) -> Result<ArcType, (Option<ArcType>, crate::Error)> {
let value = compiler
.database
.global(modulename.to_string())
.map_err(|err| (None, err))?;
Ok(value.typ)
}
}
enum UnloadedModule {
Source,
Extern(ExternModule),
}
pub struct DatabaseSnapshot {
snapshot: Option<salsa::Snapshot<CompilerDatabase>>,
}
impl Deref for DatabaseSnapshot {
type Target = CompilerDatabase;
fn deref(&self) -> &Self::Target {
self.snapshot.as_ref().unwrap()
}
}
pub struct DatabaseMut {
_import: Arc<dyn Macro>,
compiler: Option<MutexGuard<'static, CompilerDatabase>>,
}
impl Drop for DatabaseMut {
fn drop(&mut self) {
self.thread = None;
self.compiler.take();
}
}
impl Deref for DatabaseMut {
type Target = CompilerDatabase;
fn deref(&self) -> &Self::Target {
self.compiler.as_ref().unwrap()
}
}
impl DerefMut for DatabaseMut {
fn deref_mut(&mut self) -> &mut Self::Target {
self.compiler.as_mut().unwrap()
}
}
pub(crate) trait ImportApi {
fn get_module_source(
&self,
use_standard_lib: bool,
module: &str,
filename: &str,
) -> Result<Cow<'static, str>, Error>;
fn load_module(
&self,
compiler: &mut ModuleCompiler,
vm: &Thread,
module_id: &Symbol,
) -> Result<ArcType, (Option<ArcType>, MacroError)>;
fn snapshot(&self, thread: RootedThread) -> DatabaseSnapshot;
}
impl<I> ImportApi for Import<I>
where
I: Importer,
{
fn get_module_source(
&self,
use_standard_lib: bool,
module: &str,
filename: &str,
) -> Result<Cow<'static, str>, Error> {
Self::get_module_source(self, use_standard_lib, module, filename)
}
fn load_module(
&self,
compiler: &mut ModuleCompiler,
vm: &Thread,
module_id: &Symbol,
) -> Result<ArcType, (Option<ArcType>, MacroError)> {
Self::load_module(self, compiler, vm, module_id)
}
fn snapshot(&self, thread: RootedThread) -> DatabaseSnapshot {
Self::snapshot(self, thread)
}
}
pub struct Import<I = DefaultImporter> {
pub paths: RwLock<Vec<PathBuf>>,
pub loaders: RwLock<FnvMap<String, ExternLoader>>,
pub importer: I,
compiler: Mutex<CompilerDatabase>,
}
impl<I> Import<I> {
pub fn new(importer: I) -> Import<I> {
Import {
paths: RwLock::new(vec![PathBuf::from(".")]),
loaders: RwLock::default(),
compiler: CompilerDatabase::new_base(None).into(),
importer: importer,
}
}
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()
}
pub fn database_mut(self: Arc<Self>, thread: RootedThread) -> DatabaseMut
where
I: Importer,
{
let mut compiler = unsafe {
DatabaseMut {
compiler: Some(mem::transmute::<
MutexGuard<CompilerDatabase>,
MutexGuard<CompilerDatabase>,
>(self.compiler.lock().unwrap())),
_import: self,
}
};
compiler.thread = Some(thread);
compiler
}
pub fn snapshot(&self, thread: RootedThread) -> DatabaseSnapshot {
self.snapshot_(Some(thread))
}
fn snapshot_(&self, thread: Option<RootedThread>) -> DatabaseSnapshot {
let mut compiler = self.compiler.lock().unwrap();
compiler.thread = thread;
let snapshot = compiler.snapshot();
compiler.thread = None;
DatabaseSnapshot {
snapshot: Some(snapshot),
}
}
fn get_unloaded_module(&self, vm: &Thread, module: &str) -> Result<UnloadedModule, MacroError> {
{
let mut loaders = self.loaders.write().unwrap();
if let Some(loader) = loaders.get_mut(module) {
let value = loader(vm).map_err(MacroError::new)?;
return Ok(UnloadedModule::Extern(value));
}
}
Ok(UnloadedModule::Source)
}
pub(crate) fn get_module_source(
&self,
use_standard_lib: bool,
module: &str,
filename: &str,
) -> Result<Cow<'static, str>, Error> {
let mut buffer = String::new();
let std_file = if use_standard_lib {
STD_LIBS.iter().find(|tup| tup.0 == module)
} else {
None
};
Ok(match std_file {
Some(tup) => Cow::Borrowed(tup.1),
None => {
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)
.map_err(|err| Error::IO(err.into()))?;
Cow::Owned(buffer)
}
})
}
pub fn load_module(
&self,
compiler: &mut ModuleCompiler,
vm: &Thread,
module_id: &Symbol,
) -> Result<ArcType, (Option<ArcType>, MacroError)>
where
I: Importer,
{
assert!(module_id.is_global());
let modulename = module_id.name().definition_name();
let unloaded_module = self
.get_unloaded_module(vm, &modulename)
.map_err(|err| (None, MacroError::new(err)))?;
Ok(match unloaded_module {
UnloadedModule::Extern(ExternModule {
value,
typ,
metadata,
}) => {
vm.set_global(
module_id.clone(),
typ.clone(),
metadata.into(),
value.get_value(),
)
.map_err(|err| (None, MacroError::new(err)))?;
typ
}
UnloadedModule::Source => self
.importer
.import(compiler, vm, &modulename)
.map_err(|(t, err)| (t, MacroError::new(err)))?,
})
}
}
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
)
))
});
}};
}
impl<I> Macro for Import<I>
where
I: Importer,
{
fn get_capability_impl(
&self,
thread: &Thread,
arc_self: &Arc<dyn Macro>,
id: TypeId,
) -> Option<Box<dyn Any>> {
if id == TypeId::of::<Box<dyn VmEnv>>() {
Some(Box::new(
Box::new(self.snapshot(thread.root_thread())) as Box<dyn VmEnv>
))
} else if id == TypeId::of::<Arc<dyn ImportApi>>() {
Some(Box::new(
arc_self.clone().downcast_arc::<Self>().ok().unwrap() as Arc<dyn ImportApi>,
))
} else if id == TypeId::of::<DatabaseSnapshot>() {
Some(Box::new(self.snapshot(thread.root_thread())))
} else if id == TypeId::of::<DatabaseMut>() {
Some(Box::new(
arc_self
.clone()
.downcast_arc::<Self>()
.ok()
.unwrap()
.database_mut(thread.root_thread()),
))
} else {
None
}
}
fn expand(&self, macros: &mut MacroExpander, args: Vec<SpannedExpr<Symbol>>) -> MacroFuture {
fn get_module_name(args: &[SpannedExpr<Symbol>]) -> Result<String, Error> {
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).map_err(MacroError::new) {
Ok(modulename) => modulename,
Err(err) => return Box::new(future::err(err)),
};
info!("import! {}", modulename);
let db = try_future!(macros
.user_data
.downcast_ref::<CompilerDatabase>()
.ok_or_else(|| MacroError::new(Error::String(
"`import` requires a `CompilerDatabase` as user data during macro expansion".into(),
))));
Box::new(future::result(
db.import(modulename)
.map_err(|err| MacroError::message(err.to_string()))
.map(|expr| pos::spanned(args[0].span, expr)),
))
}
}
unsafe impl<I> Trace for Import<I> {
impl_trace! { self, _gc, () }
}