use std::any::Any;
use std::borrow::Cow;
use std::sync::{Arc, RwLock, Mutex};
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::{Path, PathBuf};
use base::ast::{Expr, Literal, SpannedExpr, TypedIdent};
use base::metadata::Metadata;
use base::pos;
use base::symbol::Symbol;
use vm::macros::{Macro, MacroExpander, Error as MacroError};
use vm::thread::{Thread, ThreadInternal};
use vm::internal::Value;
use super::{filename_to_module, Compiler};
use base::fnv::FnvMap;
quick_error! {
#[derive(Debug)]
pub enum Error {
CyclicDependency(module: String) {
description("Cyclic dependency")
display("Module '{}' occurs in a cyclic dependency", module)
}
String(message: String) {
description(message)
display("{}", message)
}
IO(err: io::Error) {
description(err.description())
display("{}", err)
from()
}
}
}
macro_rules! std_libs {
($($file: expr),*) => {
[$((concat!("std/", $file, ".glu"), include_str!(concat!("../std/", $file, ".glu")))),*]
}
}
static STD_LIBS: [(&'static str, &'static str); 7] = std_libs!(
"prelude",
"types",
"map",
"string",
"state",
"test",
"writer"
);
pub trait Importer: Any + Clone + Sync + Send {
fn import(
&self,
compiler: &mut Compiler,
vm: &Thread,
modulename: &str,
input: &str,
expr: SpannedExpr<Symbol>,
) -> Result<(), MacroError>;
}
#[derive(Clone)]
pub struct DefaultImporter;
impl Importer for DefaultImporter {
fn import(
&self,
compiler: &mut Compiler,
vm: &Thread,
modulename: &str,
input: &str,
expr: SpannedExpr<Symbol>,
) -> Result<(), MacroError> {
use compiler_pipeline::*;
MacroValue { expr: expr }
.load_script(compiler, vm, modulename, input, None)
.sync_or_error()?;
Ok(())
}
}
#[derive(Clone, Default)]
pub struct CheckImporter(pub Arc<Mutex<FnvMap<String, SpannedExpr<Symbol>>>>);
impl CheckImporter {
pub fn new() -> CheckImporter {
CheckImporter::default()
}
}
impl Importer for CheckImporter {
fn import(
&self,
compiler: &mut Compiler,
vm: &Thread,
module_name: &str,
input: &str,
expr: SpannedExpr<Symbol>,
) -> Result<(), MacroError> {
use compiler_pipeline::*;
let macro_value = MacroValue { expr: expr };
let TypecheckValue { expr, typ } = macro_value.typecheck(compiler, vm, module_name, input)?;
self.0.lock().unwrap().insert(module_name.into(), expr);
let metadata = Metadata::default();
vm.global_env()
.set_global(Symbol::from(module_name), typ, metadata, Value::Int(0))?;
Ok(())
}
}
pub struct Import<I = DefaultImporter> {
pub paths: RwLock<Vec<PathBuf>>,
pub importer: I,
}
impl<I> Import<I> {
pub fn new(importer: I) -> Import<I> {
Import {
paths: RwLock::new(vec![PathBuf::from(".")]),
importer: importer,
}
}
pub fn add_path<P: Into<PathBuf>>(&self, path: P) {
self.paths.write().unwrap().push(path.into());
}
pub fn read_file<P>(&self, filename: P) -> Result<Cow<'static, str>, MacroError>
where
P: AsRef<Path>,
{
self.read_file_(filename.as_ref())
}
fn read_file_(&self, filename: &Path) -> Result<Cow<'static, str>, MacroError> {
let mut buffer = String::new();
let std_file = filename
.to_str()
.and_then(|filename| STD_LIBS.iter().find(|tup| tup.0 == filename));
Ok(match std_file {
Some(tup) => Cow::Borrowed(tup.1),
None => {
let file = self.paths
.read()
.unwrap()
.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 file '{}'", filename.display()))
})?;
file.read_to_string(&mut buffer)?;
Cow::Owned(buffer)
}
})
}
}
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() }))
.downcast_mut::<State>()
.unwrap()
}
struct State {
visited: Vec<String>,
}
impl<I> Macro for Import<I>
where
I: Importer,
{
fn expand(
&self,
macros: &mut MacroExpander,
args: &mut [SpannedExpr<Symbol>],
) -> Result<SpannedExpr<Symbol>, MacroError> {
use compiler_pipeline::*;
if args.len() != 1 {
return Err(
Error::String("Expected import to get 1 argument".into()).into(),
);
}
match args[0].value {
Expr::Literal(Literal::String(ref filename)) => {
let vm = macros.vm;
let modulename = filename_to_module(filename);
let name = Symbol::from(&*modulename);
debug!("Import '{}' {:?}", modulename, get_state(macros).visited);
if !vm.global_env().global_exists(&modulename) {
{
let state = get_state(macros);
if state.visited.iter().any(|m| **m == **filename) {
return Err(Error::CyclicDependency(filename.clone()).into());
}
state.visited.push(filename.clone());
}
let file_contents = self.read_file(filename)?;
let mut compiler = Compiler::new().implicit_prelude(modulename != "std.types");
let errors = macros.errors.len();
let macro_result = file_contents
.expand_macro_with(&mut compiler, macros, &modulename)?;
if errors != macros.errors.len() {
if let Some(err) = macros.errors.pop() {
return Err(err);
}
}
get_state(macros).visited.pop();
self.importer.import(
&mut compiler,
vm,
&modulename,
&file_contents,
macro_result.expr,
)?;
}
Ok(pos::spanned(
args[0].span,
Expr::Ident(TypedIdent::new(name)),
))
}
_ => Err(
Error::String("Expected a string literal to import".into()).into(),
),
}
}
}