feat: prove elm object cache is complete
This commit is contained in:
parent
0a5e25e4f0
commit
8354d51ecb
7 changed files with 583 additions and 157 deletions
|
|
@ -21,6 +21,7 @@ structopt = { version = "0.3" }
|
|||
elm-project-utils = { path = "../../../infra/rust-elm-project-utils" }
|
||||
tracing = { version = "0.1", features = [] }
|
||||
rustc-hash = "1.1"
|
||||
home = "0.5"
|
||||
|
||||
# Required to transpile view functions to Rust
|
||||
genco = "0.15"
|
||||
|
|
|
|||
|
|
@ -12,15 +12,16 @@ type alias Model =
|
|||
, b : Array Int
|
||||
}
|
||||
|
||||
view2 : String -> Svg msg
|
||||
view2 model =
|
||||
svg [] []
|
||||
|
||||
view : String -> Html msg
|
||||
view model =
|
||||
div []
|
||||
[ text <| "Hello world" ++ model ]
|
||||
|
||||
view2 : String -> Svg msg
|
||||
view2 model =
|
||||
svg [] []
|
||||
|
||||
|
||||
view3: Bytes -> Html msg
|
||||
view3 model =
|
||||
case
|
||||
|
|
|
|||
301
src/elm.rs
Normal file
301
src/elm.rs
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
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())
|
||||
}
|
||||
}
|
||||
167
src/main.rs
167
src/main.rs
|
|
@ -7,18 +7,18 @@ use os_pipe::dup_stderr;
|
|||
use pretty::pretty;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, canonicalize, metadata};
|
||||
use std::fs::{self, canonicalize};
|
||||
use std::hash::Hasher;
|
||||
use std::io::{self, BufRead, Error, ErrorKind, Read, Write};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use structopt::StructOpt;
|
||||
use tokio;
|
||||
use tracing::{info_span, span, Instrument};
|
||||
use tracing::info_span;
|
||||
|
||||
mod elm;
|
||||
mod fixture;
|
||||
mod reporting;
|
||||
mod timings;
|
||||
|
|
@ -75,8 +75,9 @@ fn main() {
|
|||
file,
|
||||
function,
|
||||
verbosity,
|
||||
debug,
|
||||
} => {
|
||||
println!("todo transpile the code")
|
||||
transpile::transpile(file, debug, function, verbosity).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,41 +92,11 @@ fn exec(
|
|||
) -> Result<(), Problem> {
|
||||
// Our first elm make call is where we build the users program. There is a pretty good chance
|
||||
// this won't work.
|
||||
let mut command = Command::new("elm");
|
||||
command.arg("make").arg("--output").arg("/dev/null");
|
||||
//.stdin(Stdio::null());
|
||||
//.stdout(Stdio::null());
|
||||
// I am using the default inherit stderr behavior here.
|
||||
//.stderr(Stdio::piped());
|
||||
|
||||
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);
|
||||
elm::make(&file, debug, verbosity)?;
|
||||
|
||||
// Step 2, find the elm.json and elm-stuff directory
|
||||
let elm_project_dir =
|
||||
find_project_root("elm.json", "./").map_err(CompilerError::MissingElmJson)?;
|
||||
elm::find_project_root("elm.json", "./").map_err(CompilerError::MissingElmJson)?;
|
||||
|
||||
let elm_cache_dir = elm_project_dir.join("elm-stuff").join("0.19.1");
|
||||
|
||||
|
|
@ -144,31 +115,10 @@ fn exec(
|
|||
let source_checksum = hasher.finish();
|
||||
|
||||
// step 2.5 get the module name out of the file.
|
||||
let target_module = 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() {
|
||||
module_name.to_string()
|
||||
} else {
|
||||
return Err(CompilerError::CantParseModule(first_line.clone()).into());
|
||||
}
|
||||
} else {
|
||||
return Err(CompilerError::EmptyModule.into());
|
||||
};
|
||||
let elmi::Name(target_module) = elm::parse_module_name(&data)?;
|
||||
|
||||
// step 3 find all the filepaths in the elm-stuff/0.19.1/* folder
|
||||
let interfaces = load_interfaces(&elm_cache_dir)?;
|
||||
let interfaces = elm::load_interfaces(&elm_cache_dir)?;
|
||||
|
||||
// Step 5, check for the desired function
|
||||
let span = info_span!("resolved target function");
|
||||
|
|
@ -260,7 +210,7 @@ fn exec(
|
|||
// `"REPLACE_ME" + "abc" * 2000` we would have over 6k bytes to write out the new code. We
|
||||
// will have to do some extra book keeping to make sure the buffer space is big enough
|
||||
// for the replacement code.
|
||||
let mut final_script = (|| {
|
||||
let final_script = (|| {
|
||||
let mut final_script = data
|
||||
.replace("'REPLACE_ME_WITH_JSON_STRINGIFY'", "JSON.stringify(x)")
|
||||
//.replace("setTimeout", "(function(x, y) { globalThis.__bootstrap.timers.setTimeout(x, y)})")
|
||||
|
|
@ -455,7 +405,7 @@ fn exec(
|
|||
}
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
let _start = Instant::now();
|
||||
let span = info_span!("create tokio runtime");
|
||||
let timing_guard = span.enter();
|
||||
let sys = tokio::runtime::Builder::new_current_thread()
|
||||
|
|
@ -498,55 +448,6 @@ fn exec(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
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 start = Instant::now();
|
||||
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 start = Instant::now();
|
||||
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)
|
||||
}
|
||||
|
||||
struct PortableHash(ahash::AHasher);
|
||||
|
||||
impl PortableHash {
|
||||
|
|
@ -606,6 +507,8 @@ enum Arguments {
|
|||
function: String,
|
||||
#[structopt(short = "v", parse(from_occurrences))]
|
||||
verbosity: u64,
|
||||
#[structopt(long)]
|
||||
debug: bool,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -859,7 +762,7 @@ mod runtime {
|
|||
use crate::ValidatedInput;
|
||||
use deno_core::error::{type_error, AnyError};
|
||||
use deno_core::futures::FutureExt;
|
||||
use deno_core::{resolve_url, FsModuleLoader, ModuleLoader, ModuleSpecifier, OpState};
|
||||
use deno_core::{resolve_url, FsModuleLoader, ModuleLoader, ModuleSpecifier};
|
||||
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
|
||||
use deno_runtime::permissions::Permissions;
|
||||
use deno_runtime::worker::MainWorker;
|
||||
|
|
@ -867,7 +770,7 @@ mod runtime {
|
|||
use deno_runtime::BootstrapOptions;
|
||||
use deno_web::BlobStore;
|
||||
use rusty_v8 as v8;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
|
|
@ -922,7 +825,7 @@ mod runtime {
|
|||
// let main_module = resolve_url(SPECIFIER)?;
|
||||
let permissions = Permissions::allow_all();
|
||||
|
||||
let mut worker = MainWorker::from_options(main_module.clone(), permissions, options);
|
||||
let worker = MainWorker::from_options(main_module.clone(), permissions, options);
|
||||
//worker.bootstrap(&options);
|
||||
|
||||
Ok((worker, main_module))
|
||||
|
|
@ -963,7 +866,7 @@ mod runtime {
|
|||
|
||||
let this = v8::undefined(scope).into();
|
||||
|
||||
let start = Instant::now();
|
||||
let _start = Instant::now();
|
||||
let span = info_span!("dispatch v8 call");
|
||||
let timing_guard = span.enter();
|
||||
match input {
|
||||
|
|
@ -1215,37 +1118,3 @@ pub mod generated {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ impl CompilerError {
|
|||
|
||||
impl InterpreterError {
|
||||
pub fn to_doc(&self) -> Doc {
|
||||
let mut title = "COMPILER ERROR";
|
||||
let title = "COMPILER ERROR";
|
||||
use InterpreterError::*;
|
||||
let message = match self {
|
||||
Setup(data_error) => Doc::text(format!("{:?}", data_error)),
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
|||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::{self, ThreadId};
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::info_span;
|
||||
|
||||
use tracing::span::{Attributes, Record};
|
||||
use tracing::{Event, Id, Metadata, Subscriber};
|
||||
use tracing::{Event, Id, Metadata};
|
||||
|
||||
//
|
||||
// Each step will be described with a less than 100 char string. Steps will overlap.
|
||||
|
|
@ -82,7 +82,7 @@ impl ReportHandle {
|
|||
}
|
||||
|
||||
impl tracing::Subscriber for Timings {
|
||||
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
|
||||
fn enabled(&self, _metadata: &Metadata<'_>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
|||
254
src/transpile.rs
254
src/transpile.rs
|
|
@ -1 +1,255 @@
|
|||
use crate::reporting::{CompilerError, Problem, TypeError};
|
||||
use elmi::DataBinary;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{PathBuf};
|
||||
use tracing::info_span;
|
||||
use crate::elm;
|
||||
|
||||
pub fn transpile(
|
||||
file: PathBuf,
|
||||
debug: bool,
|
||||
function: String,
|
||||
//output: Option<PathBuf>,
|
||||
verbosity: u64,
|
||||
) -> Result<(), Problem> {
|
||||
// Our first elm make call is where we build the users program. There is a pretty good chance
|
||||
// this won't work.
|
||||
elm::make(&file, debug, verbosity)?;
|
||||
|
||||
// step 2 find the elm artifacts cache directory just like with exec
|
||||
let elm_project_dir =
|
||||
elm::find_project_root("elm.json", "./").map_err(CompilerError::MissingElmJson)?;
|
||||
|
||||
let elm_cache_dir = elm_project_dir.join("elm-stuff").join("0.19.1");
|
||||
|
||||
if !elm_cache_dir.is_dir() {
|
||||
return Err(CompilerError::MissingElmStuff(elm_cache_dir).into());
|
||||
}
|
||||
|
||||
let data = std::fs::read(&file)
|
||||
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, file.clone()))?;
|
||||
|
||||
let elmi::Name(target_module) = elm::parse_module_name(&data)?;
|
||||
|
||||
// Side note: the transpile function really should be taking a list of functions in modules and
|
||||
// transpiling the entire forest of dependencies. This would allow avoiding kernel javascript
|
||||
// used in the project. So I would want to reduce the tuples of file function into a set of
|
||||
// files to load. Then recursively load all dependencies.
|
||||
|
||||
// step 3 find all the filepaths in the elm-stuff/0.19.1/* folder
|
||||
let interfaces = elm::load_interfaces(&elm_cache_dir)?;
|
||||
|
||||
// Step 4, check for the desired functions have types that we can compile.
|
||||
let span = info_span!("resolved target function");
|
||||
let timing_guard = span.enter();
|
||||
match interfaces.get(&target_module) {
|
||||
Some(interface) => match interface.values.get(&elmi::Name::from(&function)) {
|
||||
Some(annotation) => {
|
||||
let elmi::CannonicalAnnotation(_free_vars, tipe) = annotation;
|
||||
|
||||
validate_function_type(tipe)?
|
||||
}
|
||||
None => return Err(CompilerError::BadImport(target_module, function).into()),
|
||||
},
|
||||
None => return Err(CompilerError::MissingModuleTypeInformation(target_module).into()),
|
||||
};
|
||||
drop(timing_guard);
|
||||
|
||||
// all the symbols in author/project will be found in the elm_cache_dir, while the rest will be
|
||||
// found in the elm_home_dir
|
||||
|
||||
// Step 5, recursively load all the symbols from the ~/.elm stuff artifacts.dat
|
||||
println!("ok the function was acceptable, ready to build symbol table");
|
||||
|
||||
let objects = elm::load_objects(&elm_cache_dir)?;
|
||||
|
||||
let entrypoint = elmi::Global(
|
||||
elmi::ModuleNameCanonical {
|
||||
package: elmi::PackageName::new("author", "project"),
|
||||
module: elmi::Name(target_module.clone()),
|
||||
},
|
||||
elmi::Name(function.clone()),
|
||||
);
|
||||
|
||||
println!("the artifacts has the symbol {}", objects.contains_key(&entrypoint));
|
||||
//let visited: HashSet<Global> = HashSet::new();
|
||||
for (_key, node) in objects.iter() {
|
||||
//println!("key {}", key);
|
||||
match node {
|
||||
elmi::Node::Define(_expr, deps) => {
|
||||
for dep in deps.iter() {
|
||||
if !objects.contains_key(&dep) {
|
||||
println!("could not find dep {}", dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
elmi::Node::DefineTailFunc(_, _, deps) => {
|
||||
for dep in deps.iter() {
|
||||
if !objects.contains_key(&dep) {
|
||||
println!("could not find dep {}", dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
elmi::Node::Ctor(_, _) => {
|
||||
}
|
||||
elmi::Node::Enum(_) => {
|
||||
}
|
||||
elmi::Node::Box => {
|
||||
}
|
||||
elmi::Node::Link(dep) => {
|
||||
if !objects.contains_key(&dep) {
|
||||
println!("could not find dep {}", dep);
|
||||
}
|
||||
}
|
||||
elmi::Node::Cycle(_, _, _, deps) => {
|
||||
for dep in deps.iter() {
|
||||
if !objects.contains_key(&dep) {
|
||||
println!("could not find dep {}", dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
elmi::Node::Manager(_) => (),
|
||||
elmi::Node::Kernel(_, deps) => {
|
||||
for dep in deps.iter() {
|
||||
if !objects.contains_key(&dep) {
|
||||
println!("could not find dep {}", dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
elmi::Node::PortIncoming(_, deps) => {
|
||||
for dep in deps.iter() {
|
||||
if !objects.contains_key(&dep) {
|
||||
println!("could not find dep {}", dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
elmi::Node::PortOutgoing(_, deps) => {
|
||||
for dep in deps.iter() {
|
||||
if !objects.contains_key(&dep) {
|
||||
println!("could not find dep {}", dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _symbol_table: HashMap<elmi::Global, elmi::Node> = HashMap::new();
|
||||
|
||||
// step 6, start generating rust code using a tree visitor on each of the entry points.
|
||||
// Accumulate the contents of each rust module in map.
|
||||
//
|
||||
// step 6a decide on the name mangling rules for elm indentifiers to rust identifiers. I can't
|
||||
// use the $ in identifiers. Maybe I could use Z. Not sure why I can't use underscore.
|
||||
// author_project__module_name__identifier?
|
||||
//
|
||||
// step 6b figure out how to handle partial apply, one approach requires me to track the arity
|
||||
// of each function call and generate a custom closure for each partial apply.
|
||||
|
||||
// step 7 write out each of the generated rust modulues.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_function_type(tipe: &elmi::Type) -> Result<(), TypeError> {
|
||||
match tipe {
|
||||
elmi::Type::TLambda(a, b) => {
|
||||
// We want to check the output types first because this is where we will figure out if
|
||||
// there is more than one argument. I only want to accept functions with a single
|
||||
// argument to keep the complexity down while I figure out how to map over to rust. I
|
||||
// am specifically worried about implementing partial application in a performant way.
|
||||
// Nested partial application worries me.
|
||||
validate_output_type(&**b)?;
|
||||
validate_input_type(&**a)?;
|
||||
Ok(())
|
||||
}
|
||||
elmi::Type::TVar(_) => Err(TypeError::CantEvalGeneric),
|
||||
elmi::Type::TType(module_name, name, args) if args.is_empty() => {
|
||||
// If our function returns a primitive type
|
||||
if module_name == "elm/core/String" && name == "String" {
|
||||
return Ok(());
|
||||
}
|
||||
if module_name == "elm/bytes/Bytes" && name == "Bytes" {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(TypeError::CantEvalType(tipe.clone()))
|
||||
}
|
||||
elmi::Type::TType(_, _, _) => Err(TypeError::CantEvalCustomType),
|
||||
elmi::Type::TRecord(_, _) => Err(TypeError::CantEvalRecord),
|
||||
elmi::Type::TUnit => Err(TypeError::CantEvalUnit),
|
||||
elmi::Type::TTuple(_, _, _) => Err(TypeError::CantEvalTuple),
|
||||
elmi::Type::TAlias(_, _, _, ref alias) => {
|
||||
match &**alias {
|
||||
elmi::AliasType::Filled(tipe) => {
|
||||
// I think the recursion is limited to a single step. I have not tested what
|
||||
// the CannonicalAnnotation would look like for a doubly indirect alias, for
|
||||
// example for `view` below
|
||||
// ```elm
|
||||
// type alias Foo = Int
|
||||
// type alias Bar = String
|
||||
//
|
||||
// type alias Zap = Foo -> Bar
|
||||
//
|
||||
// view : Zap
|
||||
// ```
|
||||
validate_function_type(tipe)
|
||||
}
|
||||
elmi::AliasType::Holey(_) => return Err(TypeError::CantEvalHoleyAlias),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_input_type(tipe: &elmi::Type) -> Result<(), TypeError> {
|
||||
match tipe {
|
||||
elmi::Type::TLambda(_, _) => Err(TypeError::EvalRequiresSingleArgument(tipe.clone())),
|
||||
elmi::Type::TType(module_name, name, args) if args.is_empty() => {
|
||||
if module_name == "elm/core/String" && name == "String" {
|
||||
Ok(())
|
||||
} else if module_name == "elm/bytes/Bytes" && name == "Bytes" {
|
||||
Ok(())
|
||||
} else if module_name == "elm/json/Json.Encode" && name == "Value" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TypeError::InputTypeNotSupported(tipe.clone()))
|
||||
}
|
||||
}
|
||||
elmi::Type::TAlias(_, _, _, ref alias) => match &**alias {
|
||||
elmi::AliasType::Filled(tipe) => validate_input_type(tipe),
|
||||
elmi::AliasType::Holey(_) => Err(TypeError::CantEvalHoleyAlias),
|
||||
},
|
||||
_ => Err(TypeError::OutputTypeNotSupported(tipe.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_output_type(tipe: &elmi::Type) -> Result<(), TypeError> {
|
||||
match tipe {
|
||||
elmi::Type::TType(module_name, name, _args) => {
|
||||
if module_name == "elm/core/String" && name == "String" {
|
||||
Ok(())
|
||||
} else if module_name == "elm/bytes/Bytes" && name == "Bytes" {
|
||||
Ok(())
|
||||
} else if module_name == "elm/json/Json.Encode" && name == "Value" {
|
||||
Ok(())
|
||||
} else if module_name == "elm/virtual-dom/VirtualDom" && name == "Node" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TypeError::OutputTypeNotSupported(tipe.clone()))
|
||||
}
|
||||
}
|
||||
elmi::Type::TAlias(_, _, _, ref alias) => match &**alias {
|
||||
elmi::AliasType::Filled(tipe) => validate_output_type(tipe),
|
||||
elmi::AliasType::Holey(_) => Err(TypeError::CantEvalHoleyAlias),
|
||||
},
|
||||
_ => Err(TypeError::OutputTypeNotSupported(tipe.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out how to do structural types. If I could name mangle all the functions I could write
|
||||
// them out in the same namespace as lambdas which would avoid the structural typing problem if the
|
||||
// lambda was used by one type. Monomorphism. But if the lambda is used by multiple types then I
|
||||
// would either need to narrow the type into a tuple at the call site, generate a specialize struct
|
||||
// and borrow all the children.
|
||||
//
|
||||
//
|
||||
// The question is should I use Rc with immutable datastructures? Or should I try to statically
|
||||
// analyse copies and use mutable state when possible.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue