starmelon/src/elm.rs

301 lines
11 KiB
Rust
Raw Normal View History

use crate::reporting::{CompilerError, Problem};
use elmi::DataBinary;
use std::collections::HashMap;
use std::fs::{self, canonicalize, metadata};
use std::io::{self, BufRead, Error, ErrorKind, Write};
use std::path::{Path, PathBuf};
use std::process::{Command};
use tracing::info_span;
pub fn make(file: &Path, debug: bool, verbosity: u64) -> Result<(), Problem> {
let mut command = Command::new("elm");
command.arg("make").arg("--output").arg("/dev/null");
if debug {
command.arg("--debug");
}
command.arg(file);
let span = info_span!("elm make target file");
let timing_guard = span.enter();
match command.output() {
Ok(output) => {
if !output.status.success() {
io::stderr().write_all(&output.stderr).unwrap();
io::stderr().write_all(&output.stdout).unwrap();
return Err(CompilerError::FailedBuildingFixture.into());
}
if verbosity > 0 {
io::stderr().write_all(&output.stderr).unwrap();
}
}
Err(_io_err) => {
// TODO handle this one
return Err(Problem::Wildcard("elm failed".into()));
}
}
drop(timing_guard);
Ok(())
}
pub fn load_interfaces(elm_cache_dir: &Path) -> Result<HashMap<String, elmi::Interface>, Problem> {
let mut interfaces = HashMap::new();
let entries = fs::read_dir(&elm_cache_dir)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()))?;
for entry in entries {
let entry = entry.map_err(|io_err| {
CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf())
})?;
let path = entry.path();
if path.is_file() {
match path.extension() {
Some(ext) if ext == "elmi" => {
// step 4 load all the modules
let parse_span = info_span!("parse elmi");
let _parse_guard = parse_span.enter();
let load_span = info_span!("file reads");
let load_guard = load_span.enter();
let data = std::fs::read(&path)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
drop(load_guard);
let (_remaining, i) = elmi::Interface::get(&data).map_err(|_err| {
CompilerError::FailedElmiParse("todo elmi parsing".to_owned())
})?;
if let Some(stem) = path.file_stem() {
// in theory the module name of the interface can be determined by
// the filename in elm 0.19.0 and 0.19.1
let module_name: String = stem
.to_string_lossy()
.split('-')
.collect::<Vec<&str>>()
.join(".");
interfaces.insert(module_name, i);
}
}
_ => (),
}
}
}
Ok(interfaces)
}
pub fn load_objects(elm_cache_dir: &Path) -> Result<HashMap<elmi::Global, elmi::Node>, Problem> {
let mut objects = HashMap::new();
let entries = fs::read_dir(&elm_cache_dir)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()))?;
for entry in entries {
let entry = entry.map_err(|io_err| {
CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf())
})?;
let path = entry.path();
if path.is_file() {
match path.file_name() {
Some(file_name) if file_name == "o.dat" => {
let data = std::fs::read(&path)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
let (_remaining, opt_global_graph) = elmi::OptGlobalGraph::get(&data).map_err(|_err| {
CompilerError::FailedElmiParse("todo elmi parsing".to_owned())
})?;
objects.extend(opt_global_graph.nodes.into_iter());
}
_ => {},
}
match path.extension() {
Some(ext) if ext == "elmo" => {
// step 4 load all the modules
let parse_span = info_span!("parse elmo");
let _parse_guard = parse_span.enter();
let load_span = info_span!("file reads");
let load_guard = load_span.enter();
let data = std::fs::read(&path)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
drop(load_guard);
let (_remaining, opt_local_graph) = elmi::OptLocalGraph::get(&data).map_err(|_err| {
CompilerError::FailedElmiParse("todo elmi parsing".to_owned())
})?;
objects.extend(opt_local_graph.nodes.into_iter());
}
_ => (),
}
}
}
Ok(objects)
}
pub struct SymbolLoader {
project_cache_directory: PathBuf,
global_cache_directory: PathBuf,
symbol_cache: HashMap<elmi::Global, elmi::Node>,
}
impl SymbolLoader {
pub fn try_load(&mut self, symbol: elmi::Global) -> Result<bool, Problem> {
if self.symbol_cache.contains_key(&symbol) {
return Ok(true);
}
let elmi::Global(
elmi::ModuleNameCanonical {
ref package,
ref module,
},
ref _identifier,
) = symbol;
if *package == elmi::PackageName::new("author", "project") {
let filename = module.0.split('.').collect::<Vec<&str>>().join("-");
let mut path = self.project_cache_directory.join(filename);
path.set_extension(".elmo");
} else {
let mut path = self.global_cache_directory.clone();
path.push(&package.author);
path.push(&package.project);
// TODO figure out the version required.
//path.push(version);
path.push("artifacts.dat");
}
Ok(false)
}
}
//pub fn load_symbol_table(elm_cache_dir: &Path) -> Result<HashMap<String, elmi::Interface>, Problem> {
// // given a global symbol, calculate the folder that contains the cache file.
// let (_remaining, i) = OptLocalGraph::get(&data).unwrap();
//
// let mut interfaces = HashMap::new();
//
// let entries = fs::read_dir(&elm_cache_dir)
// .map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()))?;
//
// for entry in entries {
// let entry = entry.map_err(|io_err| {
// CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf())
// })?;
// let path = entry.path();
// if path.is_file() {
// match path.extension() {
// Some(ext) if ext == "elmi" => {
// // step 4 load all the modules
// let parse_span = info_span!("parse elmi");
// let _parse_guard = parse_span.enter();
//
// let load_span = info_span!("file reads");
// let load_guard = load_span.enter();
// let data = std::fs::read(&path)
// .map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
// drop(load_guard);
//
// let (_remaining, i) = elmi::Interface::get(&data).map_err(|_err| {
// CompilerError::FailedElmiParse("todo elmi parsing".to_owned())
// })?;
//
// if let Some(stem) = path.file_stem() {
// // in theory the module name of the interface can be determined by
// // the filename in elm 0.19.0 and 0.19.1
// let module_name: String = stem
// .to_string_lossy()
// .split('-')
// .collect::<Vec<&str>>()
// .join(".");
// interfaces.insert(module_name, i);
// }
// }
// _ => (),
// }
// }
// }
//
// Ok(interfaces)
//}
/// Look for the cargo.toml file that we are building to determine the root directory of the project
/// (so that we can locate the source files)
pub fn find_project_root<S: AsRef<Path>>(filename: S, search: S) -> io::Result<PathBuf> {
if !search.as_ref().is_dir() {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("`{}` is not a directory", search.as_ref().display()),
));
}
let mut path_buf = canonicalize(&search)?;
let error: io::Error = Error::new(
ErrorKind::NotFound,
format!(
"could not find `{}` in `{:?}` or any parent directory",
filename.as_ref().display(),
path_buf
),
);
loop {
let test_file = path_buf.as_path().join(&filename);
match metadata(test_file) {
Ok(_) => return Ok(path_buf),
Err(_) => (),
}
match path_buf.as_path().parent() {
Some(_) => (),
None => return Err(error),
}
path_buf.pop();
}
}
pub fn get_elm_home() -> io::Result<PathBuf> {
match std::env::var("ELM_HOME") {
Ok(elm_home) => Ok(PathBuf::from(elm_home)),
Err(_) => {
if let Some(mut dir) = home::home_dir() {
dir.push(".elm");
Ok(dir)
} else {
Err(std::io::Error::new(
ErrorKind::NotFound,
"ELM_HOME not set and home directory could not be calculated by `home` crate",
))
}
}
}
}
pub fn parse_module_name(data: &[u8]) -> Result<elmi::Name, CompilerError> {
if let Some(Ok(first_line)) = data.lines().next() {
let mut tokens = first_line.split_whitespace();
match tokens.next() {
Some(token) if token == "port" => {
tokens.next();
}
Some(token) if token == "module" => (),
Some(token) if token == "effect" => {
tokens.next();
}
_ => {
return Err(CompilerError::CantParseModule(first_line.clone()).into());
}
};
if let Some(module_name) = tokens.next() {
Ok(elmi::Name(module_name.to_string()))
} else {
Err(CompilerError::CantParseModule(first_line.clone()).into())
}
} else {
Err(CompilerError::EmptyModule.into())
}
}