refactor: replace unwraps with errors
This commit is contained in:
parent
601137dd16
commit
287e59c566
3 changed files with 545 additions and 436 deletions
|
|
@ -8,7 +8,6 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
structopt = { version = "0.3" }
|
structopt = { version = "0.3" }
|
||||||
elmi = { path = "../../../infra/rust-elmi" }
|
elmi = { path = "../../../infra/rust-elmi" }
|
||||||
uuid = { version = "0.8", features = [ "v4" ] }
|
|
||||||
ahash = "0.7"
|
ahash = "0.7"
|
||||||
serde = { version = "1.0", features = [ "derive" ] }
|
serde = { version = "1.0", features = [ "derive" ] }
|
||||||
serde_json = { version ="1.0", features = [] }
|
serde_json = { version ="1.0", features = [] }
|
||||||
|
|
|
||||||
371
src/main.rs
371
src/main.rs
|
|
@ -1,11 +1,10 @@
|
||||||
extern crate naive_wadler_prettier as pretty;
|
extern crate naive_wadler_prettier as pretty;
|
||||||
use crate::reporting::{Problem, SetupError, TypeError};
|
use crate::reporting::{CompilerError, InterpreterError, Problem, SetupError, TypeError};
|
||||||
use elmi::DataBinary;
|
use elmi::DataBinary;
|
||||||
use rusty_v8 as v8;
|
use pretty::pretty;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fs::{self, canonicalize, metadata};
|
use std::fs::{self, canonicalize, metadata};
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
|
|
@ -16,7 +15,6 @@ use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use tokio;
|
use tokio;
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
mod reporting;
|
mod reporting;
|
||||||
|
|
||||||
|
|
@ -30,7 +28,22 @@ fn main() {
|
||||||
debug,
|
debug,
|
||||||
function,
|
function,
|
||||||
output,
|
output,
|
||||||
|
verbosity,
|
||||||
} => {
|
} => {
|
||||||
|
if let Err(problem) = exec(file, debug, function, output, verbosity) {
|
||||||
|
println!("{}", pretty(80, problem.to_doc()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec(
|
||||||
|
file: PathBuf,
|
||||||
|
debug: bool,
|
||||||
|
function: String,
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
verbosity: u64,
|
||||||
|
) -> Result<(), Problem> {
|
||||||
let mut command = Command::new("elm");
|
let mut command = Command::new("elm");
|
||||||
command
|
command
|
||||||
.arg("make")
|
.arg("make")
|
||||||
|
|
@ -47,35 +60,30 @@ fn main() {
|
||||||
match command.status() {
|
match command.status() {
|
||||||
Ok(exit_status) => {
|
Ok(exit_status) => {
|
||||||
if !exit_status.success() {
|
if !exit_status.success() {
|
||||||
return;
|
// TODO report the stdout from the elm compiler
|
||||||
|
return Err(Problem::Wildcard("elm failed".into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return;
|
return Err(Problem::Wildcard("elm failed".into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eprintln!("[{:?}] compiled {:?}", Instant::now() - start, file);
|
eprintln!("[{:?}] compiled {:?}", Instant::now() - start, file);
|
||||||
|
|
||||||
// Step 2, find the elm.json and elm-stuff directory
|
// Step 2, find the elm.json and elm-stuff directory
|
||||||
let elm_project_dir = match find_project_root("elm.json", "./") {
|
let elm_project_dir =
|
||||||
Ok(p) => p,
|
find_project_root("elm.json", "./").map_err(CompilerError::MissingElmJson)?;
|
||||||
Err(_) => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let elm_cache_dir = elm_project_dir.join("elm-stuff").join("0.19.1");
|
let elm_cache_dir = elm_project_dir.join("elm-stuff").join("0.19.1");
|
||||||
|
|
||||||
if !elm_cache_dir.is_dir() {
|
if !elm_cache_dir.is_dir() {
|
||||||
return;
|
return Err(CompilerError::MissingElmStuff(elm_cache_dir).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove this unwrap
|
let data = std::fs::read(&file)
|
||||||
// TODO CompilerError::
|
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, file.clone()))?;
|
||||||
let data = std::fs::read(&file).unwrap();
|
|
||||||
|
|
||||||
let mut hasher = PortableHash::new();
|
let mut hasher = PortableHash::new();
|
||||||
// TODO CompilerError::ElmJsonChecksumFailed(io::Error, PathBuf),
|
|
||||||
hasher.write_all(&data).unwrap();
|
hasher.write_all(&data).unwrap();
|
||||||
// also include the function name in the checksum so users can run multiple functions
|
// also include the function name in the checksum so users can run multiple functions
|
||||||
// from the same file but still get caching if the file does not change.
|
// from the same file but still get caching if the file does not change.
|
||||||
|
|
@ -93,59 +101,21 @@ fn main() {
|
||||||
Some(token) if token == "effect" => {
|
Some(token) if token == "effect" => {
|
||||||
tokens.next();
|
tokens.next();
|
||||||
}
|
}
|
||||||
_ => return,
|
_ => {
|
||||||
|
return Err(CompilerError::CantParseModule(first_line.clone()).into());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if let Some(module_name) = tokens.next() {
|
if let Some(module_name) = tokens.next() {
|
||||||
module_name.to_string()
|
module_name.to_string()
|
||||||
} else {
|
} else {
|
||||||
return;
|
return Err(CompilerError::CantParseModule(first_line.clone()).into());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return Err(CompilerError::EmptyModule.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
// step 3 find all the filepaths in the elm-stuff/0.19.1/* folder
|
// step 3 find all the filepaths in the elm-stuff/0.19.1/* folder
|
||||||
let interfaces = (|| -> io::Result<HashMap<String, elmi::Interface>> {
|
let interfaces = load_interfaces(&elm_cache_dir)?;
|
||||||
let mut interfaces = HashMap::new();
|
|
||||||
|
|
||||||
for entry in fs::read_dir(&elm_cache_dir)? {
|
|
||||||
let entry = entry?;
|
|
||||||
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 data = std::fs::read(&path).unwrap();
|
|
||||||
eprintln!("[{:?}] loaded {:?}", Instant::now() - start, path);
|
|
||||||
|
|
||||||
let start = Instant::now();
|
|
||||||
let (_remaining, i) = elmi::Interface::get(&data).unwrap();
|
|
||||||
|
|
||||||
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(".");
|
|
||||||
eprintln!(
|
|
||||||
"[{:?}] parsed {:?}",
|
|
||||||
Instant::now() - start,
|
|
||||||
module_name
|
|
||||||
);
|
|
||||||
interfaces.insert(module_name, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(interfaces)
|
|
||||||
})()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Step 5, check for the desired function
|
// Step 5, check for the desired function
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
@ -153,22 +123,17 @@ fn main() {
|
||||||
Some(interface) => match interface.values.get(&elmi::Name::from(&function)) {
|
Some(interface) => match interface.values.get(&elmi::Name::from(&function)) {
|
||||||
Some(annotation) => {
|
Some(annotation) => {
|
||||||
let elmi::CannonicalAnnotation(_free_vars, tipe) = annotation;
|
let elmi::CannonicalAnnotation(_free_vars, tipe) = annotation;
|
||||||
match resolve_function_type(tipe) {
|
|
||||||
Ok(x) => x,
|
resolve_function_type(tipe)?
|
||||||
Err(problem) => {
|
|
||||||
println!("problem {:?}", problem);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
None => return Err(CompilerError::CantFindFunction(function).into()),
|
||||||
}
|
|
||||||
None => return,
|
|
||||||
},
|
},
|
||||||
None => return,
|
None => return Err(CompilerError::MissingModuleTypeInformation(target_module).into()),
|
||||||
};
|
};
|
||||||
eprintln!("[{:?}] resolved target function", Instant::now() - start);
|
eprintln!("[{:?}] resolved target function", Instant::now() - start);
|
||||||
|
|
||||||
// step 6 create our private project
|
// step 6 create our private project
|
||||||
let generator_dir = setup_generator_project(elm_project_dir.clone()).unwrap();
|
let generator_dir = setup_generator_project(elm_project_dir.clone())?;
|
||||||
|
|
||||||
// step 7 create an Elm fixture file to run our function
|
// step 7 create an Elm fixture file to run our function
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
@ -183,8 +148,11 @@ fn main() {
|
||||||
let mut source_filename = generator_dir.join("src").join(&gen_module_name);
|
let mut source_filename = generator_dir.join("src").join(&gen_module_name);
|
||||||
source_filename.set_extension("elm");
|
source_filename.set_extension("elm");
|
||||||
|
|
||||||
let mut source_file = fs::File::create(&source_filename).unwrap();
|
let mut source_file = fs::File::create(&source_filename)
|
||||||
source_file.write_all(source.as_bytes()).unwrap();
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, source_filename.clone()))?;
|
||||||
|
source_file
|
||||||
|
.write_all(source.as_bytes())
|
||||||
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, source_filename.clone()))?;
|
||||||
drop(source_file);
|
drop(source_file);
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[{:?}] created generator Elm program",
|
"[{:?}] created generator Elm program",
|
||||||
|
|
@ -213,11 +181,11 @@ fn main() {
|
||||||
match command.status() {
|
match command.status() {
|
||||||
Ok(exit_status) => {
|
Ok(exit_status) => {
|
||||||
if !exit_status.success() {
|
if !exit_status.success() {
|
||||||
return;
|
return Err(CompilerError::FailedBuildingFixture.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return;
|
return Err(CompilerError::FailedBuildingFixture.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eprintln!(
|
eprintln!(
|
||||||
|
|
@ -228,7 +196,8 @@ fn main() {
|
||||||
|
|
||||||
// Step 9 fixup the compiled script to run in Deno
|
// Step 9 fixup the compiled script to run in Deno
|
||||||
|
|
||||||
let data = fs::read_to_string(&intermediate_file).unwrap();
|
let data = fs::read_to_string(&intermediate_file)
|
||||||
|
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, intermediate_file.clone()))?;
|
||||||
|
|
||||||
// TODO figure out how to replace multiple substrings in a single pass. One neat trick
|
// TODO figure out how to replace multiple substrings in a single pass. One neat trick
|
||||||
// might be to allocate enough space in our starting buffer to write the new code by
|
// might be to allocate enough space in our starting buffer to write the new code by
|
||||||
|
|
@ -240,18 +209,12 @@ fn main() {
|
||||||
.replace("'REPLACE_ME_WITH_JSON_STRINGIFY'", "JSON.stringify(x)")
|
.replace("'REPLACE_ME_WITH_JSON_STRINGIFY'", "JSON.stringify(x)")
|
||||||
.replace(";}(this));", ";}(globalThis));");
|
.replace(";}(this));", ";}(globalThis));");
|
||||||
|
|
||||||
// TODO figure out if I need the print helper
|
|
||||||
//window.print = function(msg) {
|
|
||||||
// Deno.core.print(msg);
|
|
||||||
//}
|
|
||||||
|
|
||||||
final_script.push_str("\n\n");
|
final_script.push_str("\n\n");
|
||||||
final_script.push_str(&format!("var worker = Elm.{}.init();\n", gen_module_name));
|
final_script.push_str(&format!("var worker = Elm.{}.init();\n", gen_module_name));
|
||||||
// add a short cut for invoking the function so I don't have to traverse so many object
|
// add a short cut for invoking the function so I don't have to traverse so many object
|
||||||
// lookups using the rust v8 API.
|
// lookups using the rust v8 API.
|
||||||
final_script.push_str(
|
final_script
|
||||||
"globalThis.runOnInput = function(data) { worker.ports.onInput.send(data) };\n",
|
.push_str("globalThis.runOnInput = function(data) { worker.ports.onInput.send(data) };\n");
|
||||||
);
|
|
||||||
|
|
||||||
match output_type {
|
match output_type {
|
||||||
OutputType::Value => {
|
OutputType::Value => {
|
||||||
|
|
@ -300,16 +263,16 @@ fn main() {
|
||||||
|
|
||||||
let mut final_file = generator_dir.join("bin").join(&gen_module_name);
|
let mut final_file = generator_dir.join("bin").join(&gen_module_name);
|
||||||
final_file.set_extension("js");
|
final_file.set_extension("js");
|
||||||
std::fs::write(&final_file, final_script).unwrap();
|
std::fs::write(&final_file, final_script)
|
||||||
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, final_file.clone()))?;
|
||||||
|
|
||||||
// step 10 create a v8 isolate. We need to register a different callback depending on
|
// step 10 create a v8 isolate. We need to register a different callback depending on
|
||||||
// the output type (string, or bytes)
|
// the output type (string, or bytes)
|
||||||
|
|
||||||
let (mut worker, main_module) =
|
let (mut worker, main_module) = runtime::setup_worker(&final_file.to_string_lossy())
|
||||||
runtime::setup_worker(&final_file.to_string_lossy()).unwrap();
|
.map_err(|err| InterpreterError::EventLoop(err))?;
|
||||||
|
|
||||||
let mailbox: Arc<RefCell<Option<Result<Vec<u8>, String>>>> =
|
let mailbox: Arc<RefCell<Option<Result<Vec<u8>, String>>>> = Arc::new(RefCell::new(None));
|
||||||
Arc::new(RefCell::new(None));
|
|
||||||
|
|
||||||
let mailbox_clone = Arc::clone(&mailbox);
|
let mailbox_clone = Arc::clone(&mailbox);
|
||||||
worker.js_runtime.register_op(
|
worker.js_runtime.register_op(
|
||||||
|
|
@ -354,45 +317,7 @@ fn main() {
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(async move {
|
.block_on(async move { runtime::xyz(worker, main_module).await })?;
|
||||||
// step 10 load the module into our v8 isolate
|
|
||||||
worker.execute_module(&main_module).await.unwrap();
|
|
||||||
//worker.execute_script("somelocation", r#"runOnInput("hello from v8");"#);
|
|
||||||
let wait_for_inspector = false;
|
|
||||||
worker.run_event_loop(wait_for_inspector).await.unwrap();
|
|
||||||
|
|
||||||
{
|
|
||||||
let scope = &mut worker.js_runtime.handle_scope();
|
|
||||||
let ctx = scope.get_current_context();
|
|
||||||
let global = ctx.global(scope);
|
|
||||||
|
|
||||||
let runOnInput = {
|
|
||||||
let x = v8::String::new(scope, "runOnInput").unwrap();
|
|
||||||
v8::Local::new(scope, x).into()
|
|
||||||
};
|
|
||||||
let v8_value = global.get(scope, runOnInput).unwrap();
|
|
||||||
|
|
||||||
// step 11 marshal the input into the v8 isolate. If we are reading from an
|
|
||||||
// input file load that, if we are reading from stdin read that.
|
|
||||||
let string_input = "Foo bar zap".to_owned();
|
|
||||||
|
|
||||||
// step 12 invoke the function
|
|
||||||
let function = v8::Local::<v8::Function>::try_from(v8_value).unwrap();
|
|
||||||
|
|
||||||
let this = v8::undefined(scope).into();
|
|
||||||
|
|
||||||
let arg1 = {
|
|
||||||
let x = v8::String::new(scope, &string_input).unwrap();
|
|
||||||
v8::Local::new(scope, x).into()
|
|
||||||
};
|
|
||||||
|
|
||||||
let start = Instant::now();
|
|
||||||
function.call(scope, this, &[arg1]);
|
|
||||||
eprintln!("\tcall dispatched {:?}", Instant::now() - start);
|
|
||||||
}
|
|
||||||
worker.run_event_loop(wait_for_inspector).await.unwrap();
|
|
||||||
eprintln!("finished waiting on runtime");
|
|
||||||
});
|
|
||||||
eprintln!("[{:?}] eval javascript", Instant::now() - start);
|
eprintln!("[{:?}] eval javascript", Instant::now() - start);
|
||||||
|
|
||||||
// step 13 receive the callback
|
// step 13 receive the callback
|
||||||
|
|
@ -402,38 +327,62 @@ fn main() {
|
||||||
// mailbox is currently borrowed
|
// mailbox is currently borrowed
|
||||||
match mailbox.replace(None) {
|
match mailbox.replace(None) {
|
||||||
Some(Ok(output)) => {
|
Some(Ok(output)) => {
|
||||||
io::stdout().write_all(&output).unwrap();
|
io::stdout()
|
||||||
|
.write_all(&output)
|
||||||
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, "stdout".into()))?;
|
||||||
}
|
}
|
||||||
Some(Err(problem)) => {
|
Some(Err(problem)) => {
|
||||||
println!("had a problem {}", problem);
|
println!("had a problem {}", problem);
|
||||||
}
|
}
|
||||||
None => println!("nothing in the mailbox"),
|
None => println!("nothing in the mailbox"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 data = std::fs::read(&path)
|
||||||
|
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
|
||||||
|
eprintln!("[{:?}] loaded {:?}", Instant::now() - start, path);
|
||||||
|
|
||||||
|
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(".");
|
||||||
|
eprintln!("[{:?}] parsed {:?}", Instant::now() - start, module_name);
|
||||||
|
interfaces.insert(module_name, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// step 6 generate the rust code type definitions, using the built in types when possible.
|
_ => (),
|
||||||
// step 7 assume someone already wrote the rust code to construct that type and expose it as dylib symbol in
|
}
|
||||||
// lib.rs as fn init() -> T
|
}
|
||||||
//
|
}
|
||||||
// step 8 run cargo build
|
|
||||||
//
|
Ok(interfaces)
|
||||||
// step 9 generate a PrivateMain worker using the view function
|
|
||||||
//
|
|
||||||
// step 10 load that private main into a v8 isolate
|
|
||||||
//
|
|
||||||
// step 11 load the dynamic library produced
|
|
||||||
//
|
|
||||||
// step 12 invoke the dynamic library function to get the data
|
|
||||||
//
|
|
||||||
// step 13 serialize the value
|
|
||||||
//
|
|
||||||
// step 14 put that value into the memory of the v8
|
|
||||||
// - parse json value from string and run a generated decoder
|
|
||||||
// - eval a script that invokes the construtor functions directly
|
|
||||||
//
|
|
||||||
// step 15 invoke the wrapped view function
|
|
||||||
//
|
|
||||||
// step 16 take the string output and write it to a file if provided, or stdout
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PortableHash(ahash::AHasher);
|
struct PortableHash(ahash::AHasher);
|
||||||
|
|
@ -485,6 +434,8 @@ enum Arguments {
|
||||||
debug: bool,
|
debug: bool,
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
|
#[structopt(short = "v", parse(from_occurrences))]
|
||||||
|
verbosity: u64,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -525,7 +476,7 @@ fn resolve_function_type(tipe: &elmi::Type) -> Result<(Option<InputType>, Output
|
||||||
|
|
||||||
Err(TypeError::CantEvalType(tipe.clone()))
|
Err(TypeError::CantEvalType(tipe.clone()))
|
||||||
}
|
}
|
||||||
elmi::Type::TType(module_name, name, args) => Err(TypeError::CantEvalCustomType),
|
elmi::Type::TType(_, _, _) => Err(TypeError::CantEvalCustomType),
|
||||||
elmi::Type::TRecord(_, _) => Err(TypeError::CantEvalRecord),
|
elmi::Type::TRecord(_, _) => Err(TypeError::CantEvalRecord),
|
||||||
elmi::Type::TUnit => Err(TypeError::CantEvalUnit),
|
elmi::Type::TUnit => Err(TypeError::CantEvalUnit),
|
||||||
elmi::Type::TTuple(_, _, _) => Err(TypeError::CantEvalTuple),
|
elmi::Type::TTuple(_, _, _) => Err(TypeError::CantEvalTuple),
|
||||||
|
|
@ -575,7 +526,7 @@ fn resolve_input_type(tipe: &elmi::Type) -> Result<InputType, TypeError> {
|
||||||
|
|
||||||
fn resolve_output_type(tipe: &elmi::Type) -> Result<OutputType, TypeError> {
|
fn resolve_output_type(tipe: &elmi::Type) -> Result<OutputType, TypeError> {
|
||||||
match tipe {
|
match tipe {
|
||||||
elmi::Type::TType(module_name, name, args) => {
|
elmi::Type::TType(module_name, name, _args) => {
|
||||||
if module_name == "elm/core/String" && name == "String" {
|
if module_name == "elm/core/String" && name == "String" {
|
||||||
Ok(OutputType::String)
|
Ok(OutputType::String)
|
||||||
} else if module_name == "elm/bytes/Bytes" && name == "Bytes" {
|
} else if module_name == "elm/bytes/Bytes" && name == "Bytes" {
|
||||||
|
|
@ -602,7 +553,8 @@ fn setup_generator_project(elm_project_dir: PathBuf) -> Result<PathBuf, Problem>
|
||||||
let our_temp_dir = elm_project_dir.join("elm-stuff").join("starmelon-5d9ecc");
|
let our_temp_dir = elm_project_dir.join("elm-stuff").join("starmelon-5d9ecc");
|
||||||
|
|
||||||
if !our_temp_dir.exists() {
|
if !our_temp_dir.exists() {
|
||||||
fs::create_dir(&our_temp_dir);
|
fs::create_dir(&our_temp_dir)
|
||||||
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, our_temp_dir.clone()))?;
|
||||||
}
|
}
|
||||||
let source_dir = our_temp_dir.join("src");
|
let source_dir = our_temp_dir.join("src");
|
||||||
let bin_dir = our_temp_dir.join("bin");
|
let bin_dir = our_temp_dir.join("bin");
|
||||||
|
|
@ -612,7 +564,10 @@ fn setup_generator_project(elm_project_dir: PathBuf) -> Result<PathBuf, Problem>
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
let elm_json_path = elm_project_dir.join("elm.json");
|
let elm_json_path = elm_project_dir.join("elm.json");
|
||||||
let mut elm_json_file = std::fs::File::open(&elm_json_path).unwrap();
|
let mut elm_json_file = std::fs::File::open(&elm_json_path)
|
||||||
|
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_json_path.clone()))?;
|
||||||
|
// TODO CompilerError::ElmJsonChecksumFailed(io::Error, PathBuf),
|
||||||
|
// ReadInputFailed(io::Error, PathBuf),
|
||||||
|
|
||||||
let mut hasher = PortableHash::new();
|
let mut hasher = PortableHash::new();
|
||||||
std::io::copy(&mut elm_json_file, &mut hasher).unwrap();
|
std::io::copy(&mut elm_json_file, &mut hasher).unwrap();
|
||||||
|
|
@ -621,41 +576,58 @@ fn setup_generator_project(elm_project_dir: PathBuf) -> Result<PathBuf, Problem>
|
||||||
|
|
||||||
let checksum_file = our_temp_dir.join("elm-json-checksum");
|
let checksum_file = our_temp_dir.join("elm-json-checksum");
|
||||||
if checksum_file.exists() {
|
if checksum_file.exists() {
|
||||||
let existing_checksum = String::from_utf8_lossy(&std::fs::read(&checksum_file).unwrap())
|
let mut raw_checksum = std::fs::read(&checksum_file)
|
||||||
|
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, checksum_file.clone()))?;
|
||||||
|
let existing_checksum = String::from_utf8_lossy(&raw_checksum)
|
||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
.unwrap();
|
.map_err(move |_parse_error| {
|
||||||
|
raw_checksum.truncate(60);
|
||||||
|
CompilerError::CorruptedChecksum(String::from_utf8_lossy(&raw_checksum).to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
if existing_checksum == checksum {
|
if existing_checksum == checksum {
|
||||||
return Ok(our_temp_dir);
|
return Ok(our_temp_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut f = std::fs::File::create(checksum_file).unwrap();
|
let mut f = std::fs::File::create(&checksum_file)
|
||||||
<std::fs::File as std::io::Write>::write(&mut f, checksum.to_string().as_bytes()).unwrap();
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, checksum_file.clone()))?;
|
||||||
|
<std::fs::File as std::io::Write>::write(&mut f, checksum.to_string().as_bytes())
|
||||||
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, checksum_file.clone()))?;
|
||||||
drop(f);
|
drop(f);
|
||||||
|
|
||||||
|
let generator_elm_json_path = our_temp_dir.join("elm.json");
|
||||||
let mut generator_elm_json_file =
|
let mut generator_elm_json_file =
|
||||||
std::fs::File::create(&our_temp_dir.join("elm.json")).unwrap();
|
std::fs::File::create(&generator_elm_json_path).map_err(|io_err| {
|
||||||
elm_json_file.rewind().unwrap();
|
CompilerError::WriteOutputFailed(io_err, generator_elm_json_path.clone())
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut data = std::fs::read(&elm_json_path).unwrap();
|
elm_json_file
|
||||||
|
.rewind()
|
||||||
|
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_json_path.clone()))?;
|
||||||
|
|
||||||
let mut elm_json = serde_json::from_slice::<ElmApplication>(&mut data).unwrap();
|
let mut data = std::fs::read(&elm_json_path)
|
||||||
// We need to modify the elm.json's source_directories field
|
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_json_path.clone()))?;
|
||||||
|
|
||||||
|
let mut elm_json = serde_json::from_slice::<ElmApplication>(&mut data)
|
||||||
|
.map_err(|err| CompilerError::FailedParseElmJson(err))?;
|
||||||
|
|
||||||
//
|
|
||||||
let mut new_src_directories = Vec::with_capacity(elm_json.source_directories.len() + 1);
|
let mut new_src_directories = Vec::with_capacity(elm_json.source_directories.len() + 1);
|
||||||
for dirname in elm_json.source_directories.iter() {
|
for dirname in elm_json.source_directories.iter() {
|
||||||
let dir = canonicalize(elm_project_dir.join(dirname)).unwrap();
|
let dir_path = elm_project_dir.join(dirname);
|
||||||
|
let dir = canonicalize(&dir_path)
|
||||||
|
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, dir_path.clone()))?;
|
||||||
new_src_directories.push(dir.to_string_lossy().to_string());
|
new_src_directories.push(dir.to_string_lossy().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::fs::create_dir(&source_dir).unwrap();
|
std::fs::create_dir(&source_dir)
|
||||||
std::fs::create_dir(&bin_dir).unwrap();
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, source_dir.clone()))?;
|
||||||
|
std::fs::create_dir(&bin_dir)
|
||||||
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, bin_dir.clone()))?;
|
||||||
|
|
||||||
new_src_directories.push(
|
new_src_directories.push(
|
||||||
canonicalize(our_temp_dir.join("src"))
|
canonicalize(our_temp_dir.join("src"))
|
||||||
.unwrap()
|
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, our_temp_dir.join("src")))?
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
|
|
@ -664,7 +636,9 @@ fn setup_generator_project(elm_project_dir: PathBuf) -> Result<PathBuf, Problem>
|
||||||
|
|
||||||
eprintln!("modified json {:?}", elm_json);
|
eprintln!("modified json {:?}", elm_json);
|
||||||
|
|
||||||
serde_json::to_writer(&mut generator_elm_json_file, &elm_json).unwrap();
|
serde_json::to_writer(&mut generator_elm_json_file, &elm_json).map_err(|io_err| {
|
||||||
|
CompilerError::WriteElmJsonFailed(io_err, generator_elm_json_path.clone())
|
||||||
|
})?;
|
||||||
|
|
||||||
elm_install(&our_temp_dir, "ThinkAlexandria/elm-html-in-elm")?;
|
elm_install(&our_temp_dir, "ThinkAlexandria/elm-html-in-elm")?;
|
||||||
elm_install(&our_temp_dir, "elm/json")?;
|
elm_install(&our_temp_dir, "elm/json")?;
|
||||||
|
|
@ -773,25 +747,22 @@ fn generate_fixture<S: AsRef<str>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
mod runtime {
|
mod runtime {
|
||||||
|
use crate::reporting::InterpreterError;
|
||||||
use deno_core::error::{type_error, AnyError};
|
use deno_core::error::{type_error, AnyError};
|
||||||
use deno_core::futures::FutureExt;
|
use deno_core::futures::FutureExt;
|
||||||
use deno_core::{op_sync, resolve_url, FsModuleLoader, ModuleLoader, ModuleSpecifier, OpState};
|
use deno_core::{resolve_url, FsModuleLoader, ModuleLoader, ModuleSpecifier, OpState};
|
||||||
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
|
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
|
||||||
use deno_runtime::permissions::Permissions;
|
use deno_runtime::permissions::Permissions;
|
||||||
use deno_runtime::worker::MainWorker;
|
use deno_runtime::worker::MainWorker;
|
||||||
use deno_runtime::worker::WorkerOptions;
|
use deno_runtime::worker::WorkerOptions;
|
||||||
use deno_web::BlobStore;
|
use deno_web::BlobStore;
|
||||||
use futures::future::lazy;
|
|
||||||
use rusty_v8 as v8;
|
use rusty_v8 as v8;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::path::Path;
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::task::Context;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tokio::sync::mpsc::unbounded_channel;
|
|
||||||
|
|
||||||
fn get_error_class_name(e: &AnyError) -> &'static str {
|
fn get_error_class_name(e: &AnyError) -> &'static str {
|
||||||
deno_runtime::errors::get_error_class_name(e).unwrap_or("Error")
|
deno_runtime::errors::get_error_class_name(e).unwrap_or("Error")
|
||||||
|
|
@ -839,6 +810,54 @@ mod runtime {
|
||||||
Ok((worker, main_module))
|
Ok((worker, main_module))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn xyz(
|
||||||
|
mut worker: MainWorker,
|
||||||
|
main_module: ModuleSpecifier,
|
||||||
|
) -> Result<(), InterpreterError> {
|
||||||
|
let wait_for_inspector = false;
|
||||||
|
// step 10 load the module into our v8 isolate
|
||||||
|
worker.execute_module(&main_module).await?;
|
||||||
|
worker.run_event_loop(wait_for_inspector).await?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let scope = &mut worker.js_runtime.handle_scope();
|
||||||
|
let ctx = scope.get_current_context();
|
||||||
|
let global = ctx.global(scope);
|
||||||
|
|
||||||
|
let runOnInput = {
|
||||||
|
let x = v8::String::new(scope, "runOnInput")
|
||||||
|
.ok_or(InterpreterError::AllocationFailed)?;
|
||||||
|
v8::Local::new(scope, x).into()
|
||||||
|
};
|
||||||
|
let v8_value = global
|
||||||
|
.get(scope, runOnInput)
|
||||||
|
.ok_or(InterpreterError::ReferenceError)?;
|
||||||
|
|
||||||
|
// step 11 marshal the input into the v8 isolate. If we are reading from an
|
||||||
|
// input file load that, if we are reading from stdin read that.
|
||||||
|
let string_input = "Foo bar zap".to_owned();
|
||||||
|
|
||||||
|
// step 12 invoke the function
|
||||||
|
let function = v8::Local::<v8::Function>::try_from(v8_value)?;
|
||||||
|
|
||||||
|
let this = v8::undefined(scope).into();
|
||||||
|
|
||||||
|
let arg1 = {
|
||||||
|
let x = v8::String::new(scope, &string_input)
|
||||||
|
.ok_or(InterpreterError::AllocationFailed)?;
|
||||||
|
v8::Local::new(scope, x).into()
|
||||||
|
};
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
function.call(scope, this, &[arg1]);
|
||||||
|
eprintln!("\tcall dispatched {:?}", Instant::now() - start);
|
||||||
|
}
|
||||||
|
worker.run_event_loop(wait_for_inspector).await?;
|
||||||
|
eprintln!("finished waiting on runtime");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
const SPECIFIER: &str = "file://$deno$/bundle.js";
|
const SPECIFIER: &str = "file://$deno$/bundle.js";
|
||||||
|
|
||||||
struct EmbeddedModuleLoader(String);
|
struct EmbeddedModuleLoader(String);
|
||||||
|
|
|
||||||
105
src/reporting.rs
105
src/reporting.rs
|
|
@ -1,8 +1,11 @@
|
||||||
use deno_core::error::{type_error, AnyError};
|
use deno_core::error::AnyError;
|
||||||
use elmi;
|
use elmi;
|
||||||
use pretty::{self, hang, hardline, hcat, hsep, sep, space_join, Doc};
|
use pretty::{self, cyan, hang, hardline, hcat, hsep, sep, space_join, Doc};
|
||||||
use rusty_v8;
|
use rusty_v8;
|
||||||
|
use serde_json;
|
||||||
|
use std::cmp::max;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -11,6 +14,19 @@ pub enum Problem {
|
||||||
BadCompiler(CompilerError),
|
BadCompiler(CompilerError),
|
||||||
BadSetup(SetupError),
|
BadSetup(SetupError),
|
||||||
BadRuntime(InterpreterError),
|
BadRuntime(InterpreterError),
|
||||||
|
Wildcard(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Problem {
|
||||||
|
pub fn to_doc(&self) -> Doc {
|
||||||
|
match self {
|
||||||
|
Problem::BadTypes(err) => err.to_doc(),
|
||||||
|
Problem::BadCompiler(err) => err.to_doc(),
|
||||||
|
Problem::BadSetup(err) => err.to_doc(),
|
||||||
|
Problem::BadRuntime(err) => err.to_doc(),
|
||||||
|
Problem::Wildcard(err) => Doc::text(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TypeError> for Problem {
|
impl From<TypeError> for Problem {
|
||||||
|
|
@ -31,6 +47,12 @@ impl From<SetupError> for Problem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<InterpreterError> for Problem {
|
||||||
|
fn from(error: InterpreterError) -> Self {
|
||||||
|
Self::BadRuntime(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TypeError {
|
pub enum TypeError {
|
||||||
CantEvalRecord,
|
CantEvalRecord,
|
||||||
|
|
@ -45,16 +67,21 @@ pub enum TypeError {
|
||||||
EvalRequiresSingleArgument(elmi::Type),
|
EvalRequiresSingleArgument(elmi::Type),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum SetupError {
|
|
||||||
InstallDependencyFailed,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CompilerError {
|
pub enum CompilerError {
|
||||||
|
MissingElmJson(io::Error),
|
||||||
|
MissingElmStuff(PathBuf),
|
||||||
|
CantParseModule(String), // first line
|
||||||
|
EmptyModule,
|
||||||
|
MissingModuleTypeInformation(String),
|
||||||
|
CantFindFunction(String),
|
||||||
|
FailedBuildingFixture,
|
||||||
ElmJsonChecksumFailed(io::Error, PathBuf),
|
ElmJsonChecksumFailed(io::Error, PathBuf),
|
||||||
ReadInputFailed(io::Error, PathBuf),
|
ReadInputFailed(io::Error, PathBuf),
|
||||||
|
CorruptedChecksum(String),
|
||||||
WriteOutputFailed(io::Error, PathBuf),
|
WriteOutputFailed(io::Error, PathBuf),
|
||||||
|
WriteElmJsonFailed(serde_json::Error, PathBuf),
|
||||||
|
FailedParseElmJson(serde_json::Error),
|
||||||
FailedElmiParse(String),
|
FailedElmiParse(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,4 +89,68 @@ pub enum CompilerError {
|
||||||
pub enum InterpreterError {
|
pub enum InterpreterError {
|
||||||
Setup(rusty_v8::DataError),
|
Setup(rusty_v8::DataError),
|
||||||
EventLoop(AnyError),
|
EventLoop(AnyError),
|
||||||
|
AllocationFailed,
|
||||||
|
ReferenceError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AnyError> for InterpreterError {
|
||||||
|
fn from(error: AnyError) -> Self {
|
||||||
|
Self::EventLoop(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<rusty_v8::DataError> for InterpreterError {
|
||||||
|
fn from(error: rusty_v8::DataError) -> Self {
|
||||||
|
Self::Setup(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeError {
|
||||||
|
pub fn to_doc(&self) -> Doc {
|
||||||
|
Doc::text("type error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SetupError {
|
||||||
|
InstallDependencyFailed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SetupError {
|
||||||
|
pub fn to_doc(&self) -> Doc {
|
||||||
|
Doc::text("setup error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompilerError {
|
||||||
|
pub fn to_doc(&self) -> Doc {
|
||||||
|
Doc::text("compiler error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterpreterError {
|
||||||
|
pub fn to_doc(&self) -> Doc {
|
||||||
|
Doc::text("interpreter error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_message_bar(title: String, file_path: &Path) -> Doc {
|
||||||
|
let used_space = 4 + title.len() + 1 + file_path.to_string_lossy().len();
|
||||||
|
|
||||||
|
cyan(Doc::text(format!(
|
||||||
|
"-- {} {} {}",
|
||||||
|
title,
|
||||||
|
std::iter::repeat('-')
|
||||||
|
.take(max(1, 80 - used_space))
|
||||||
|
.collect::<String>(),
|
||||||
|
file_path.display(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod doc {
|
||||||
|
use pretty::Doc;
|
||||||
|
|
||||||
|
pub fn reflow<S: AsRef<str>>(paragraph: S) -> Doc {
|
||||||
|
pretty::fill_words(paragraph)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue