2022-01-07 20:00:05 +00:00
|
|
|
use crate::elm;
|
2022-01-11 18:15:16 +00:00
|
|
|
use crate::reporting::{CompilerError, Problem};
|
2022-01-07 20:00:05 +00:00
|
|
|
use crate::PortableHash;
|
|
|
|
|
use std::hash::Hasher;
|
2022-01-12 04:56:15 +00:00
|
|
|
use std::io::Write;
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
use tracing::info_span;
|
2022-01-07 20:00:05 +00:00
|
|
|
|
2022-01-11 18:15:16 +00:00
|
|
|
mod astrid_pages;
|
2022-04-02 23:54:12 +01:00
|
|
|
mod css_in_elm;
|
2022-01-08 15:58:32 +00:00
|
|
|
mod fixtures;
|
2022-01-11 18:15:16 +00:00
|
|
|
mod scripting;
|
2022-01-07 20:00:05 +00:00
|
|
|
|
|
|
|
|
pub(crate) fn exec(
|
|
|
|
|
file: PathBuf,
|
|
|
|
|
debug: bool,
|
|
|
|
|
function: String,
|
|
|
|
|
input_source: Option<PathBuf>,
|
|
|
|
|
output: Option<PathBuf>,
|
|
|
|
|
verbosity: u64,
|
|
|
|
|
sqlite_path: Option<PathBuf>,
|
|
|
|
|
) -> 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.json and elm-stuff directory
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-21 20:07:19 +00:00
|
|
|
// step 2.5 get the module name out of the file.
|
2022-01-07 20:00:05 +00:00
|
|
|
let data = std::fs::read(&file)
|
|
|
|
|
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, file.clone()))?;
|
|
|
|
|
|
|
|
|
|
let entrypoint = elmi::Global(
|
|
|
|
|
elmi::ModuleNameCanonical {
|
|
|
|
|
package: elmi::PackageName::new("author", "project"),
|
|
|
|
|
module: elm::parse_module_name(&data)?,
|
|
|
|
|
},
|
|
|
|
|
elmi::Name(function.clone()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// step 3 find all the filepaths in the elm-stuff/0.19.1/* folder
|
|
|
|
|
let interfaces = elm::load_interfaces(&elm_cache_dir)?;
|
|
|
|
|
|
|
|
|
|
// Step 5, check for the desired function
|
|
|
|
|
let span = info_span!("resolved target function");
|
|
|
|
|
let timing_guard = span.enter();
|
2022-01-11 18:15:16 +00:00
|
|
|
let mode = match interfaces.get(&entrypoint.0) {
|
2022-01-07 20:00:05 +00:00
|
|
|
Some(interface) => match interface.values.get(&entrypoint.1) {
|
|
|
|
|
Some(annotation) => {
|
|
|
|
|
let elmi::CannonicalAnnotation(_free_vars, tipe) = annotation;
|
|
|
|
|
|
2022-01-11 18:15:16 +00:00
|
|
|
if is_astrid_pages_route(tipe) {
|
|
|
|
|
ExecutionMode::AstridPagesRoute
|
2022-04-02 23:54:12 +01:00
|
|
|
} else if is_css_in_elm_stylesheet(tipe) {
|
|
|
|
|
ExecutionMode::CssInElm
|
2022-01-11 18:15:16 +00:00
|
|
|
} else {
|
|
|
|
|
let (input_type, output_type) = scripting::resolve_function_type(tipe)?;
|
|
|
|
|
ExecutionMode::Scripting(input_type, output_type)
|
|
|
|
|
}
|
2022-01-07 20:00:05 +00:00
|
|
|
}
|
|
|
|
|
None => return Err(CompilerError::BadImport(entrypoint).into()),
|
|
|
|
|
},
|
|
|
|
|
None => return Err(CompilerError::MissingModuleTypeInformation(entrypoint.0).into()),
|
|
|
|
|
};
|
|
|
|
|
drop(timing_guard);
|
|
|
|
|
|
2022-03-21 20:07:19 +00:00
|
|
|
let mut hasher = PortableHash::new();
|
|
|
|
|
hasher.write_all(&data).unwrap();
|
|
|
|
|
// 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.
|
|
|
|
|
hasher.write_all(function.as_bytes()).unwrap();
|
|
|
|
|
let source_checksum = hasher.finish();
|
|
|
|
|
|
2022-01-11 18:15:16 +00:00
|
|
|
match mode {
|
|
|
|
|
ExecutionMode::Scripting(input_type, output_type) => scripting::run(
|
|
|
|
|
debug,
|
|
|
|
|
verbosity,
|
|
|
|
|
sqlite_path,
|
|
|
|
|
elm_project_dir,
|
|
|
|
|
source_checksum,
|
|
|
|
|
entrypoint,
|
|
|
|
|
input_type,
|
|
|
|
|
output_type,
|
|
|
|
|
input_source,
|
|
|
|
|
output,
|
|
|
|
|
),
|
|
|
|
|
ExecutionMode::AstridPagesRoute => astrid_pages::run(
|
|
|
|
|
debug,
|
|
|
|
|
verbosity,
|
|
|
|
|
sqlite_path,
|
|
|
|
|
elm_project_dir,
|
|
|
|
|
source_checksum,
|
|
|
|
|
entrypoint,
|
|
|
|
|
output,
|
|
|
|
|
),
|
2022-04-02 23:54:12 +01:00
|
|
|
ExecutionMode::CssInElm => css_in_elm::run(
|
|
|
|
|
debug,
|
|
|
|
|
verbosity,
|
|
|
|
|
elm_project_dir,
|
|
|
|
|
source_checksum,
|
|
|
|
|
entrypoint,
|
|
|
|
|
output,
|
|
|
|
|
),
|
2022-01-07 20:00:05 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-11 18:15:16 +00:00
|
|
|
enum ExecutionMode {
|
|
|
|
|
AstridPagesRoute,
|
2022-04-02 23:54:12 +01:00
|
|
|
CssInElm,
|
2022-01-11 18:15:16 +00:00
|
|
|
Scripting(Option<scripting::InputType>, scripting::OutputType),
|
2022-01-07 20:00:05 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-11 18:15:16 +00:00
|
|
|
fn is_astrid_pages_route(tipe: &elmi::Type) -> bool {
|
2022-01-07 20:00:05 +00:00
|
|
|
match tipe {
|
|
|
|
|
elmi::Type::TType(module_name, name, _args) => {
|
2022-01-11 18:15:16 +00:00
|
|
|
module_name == "author/project/Astrid.Pages" && name == "Route"
|
2022-01-07 20:00:05 +00:00
|
|
|
}
|
2022-01-11 18:15:16 +00:00
|
|
|
_ => false,
|
2022-01-07 20:00:05 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-02 23:54:12 +01:00
|
|
|
fn is_css_in_elm_stylesheet(tipe: &elmi::Type) -> bool {
|
|
|
|
|
match tipe {
|
|
|
|
|
elmi::Type::TType(module_name, name, args) => {
|
|
|
|
|
if module_name == "elm/core/List" && name == "List" && args.len() == 1 {
|
|
|
|
|
match &args[0] {
|
|
|
|
|
elmi::Type::TTuple(a, b, None) => {
|
|
|
|
|
match &**a {
|
|
|
|
|
elmi::Type::TType(module_name, name, _)
|
2022-04-03 00:15:07 +01:00
|
|
|
if module_name == "elm/core/String" && name == "String" =>
|
|
|
|
|
{
|
|
|
|
|
()
|
|
|
|
|
}
|
2022-04-02 23:54:12 +01:00
|
|
|
_ => return false,
|
|
|
|
|
}
|
|
|
|
|
match &**b {
|
|
|
|
|
elmi::Type::TType(module_name, name, args)
|
2022-04-03 00:15:07 +01:00
|
|
|
if module_name == "elm/core/List"
|
|
|
|
|
&& name == "List"
|
|
|
|
|
&& args.len() == 1 =>
|
2022-04-02 23:54:12 +01:00
|
|
|
{
|
|
|
|
|
match &args[0] {
|
|
|
|
|
elmi::Type::TAlias(module_name, name, _, _)
|
2022-04-03 00:15:07 +01:00
|
|
|
if module_name == "ThinkAlexandria/css-in-elm/Css"
|
|
|
|
|
&& name == "Stylesheet" =>
|
|
|
|
|
{
|
|
|
|
|
return true
|
|
|
|
|
}
|
2022-04-02 23:54:12 +01:00
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-07 20:00:05 +00:00
|
|
|
mod runtime {
|
|
|
|
|
use crate::reporting::InterpreterError;
|
|
|
|
|
use deno_core::error::{type_error, AnyError};
|
|
|
|
|
use deno_core::futures::FutureExt;
|
2022-03-21 20:07:19 +00:00
|
|
|
use deno_core::{resolve_url, Extension, FsModuleLoader, ModuleLoader, ModuleSpecifier};
|
2022-01-07 20:00:05 +00:00
|
|
|
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
|
|
|
|
|
use deno_runtime::permissions::Permissions;
|
|
|
|
|
use deno_runtime::worker::MainWorker;
|
|
|
|
|
use deno_runtime::worker::WorkerOptions;
|
|
|
|
|
use deno_runtime::BootstrapOptions;
|
|
|
|
|
use deno_web::BlobStore;
|
|
|
|
|
use std::pin::Pin;
|
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
use tracing::{info_span, Instrument};
|
|
|
|
|
|
|
|
|
|
fn get_error_class_name(e: &AnyError) -> &'static str {
|
|
|
|
|
deno_runtime::errors::get_error_class_name(e).unwrap_or("Error")
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-21 20:07:19 +00:00
|
|
|
pub fn setup_worker(
|
|
|
|
|
extensions: Vec<Extension>,
|
|
|
|
|
path_str: &str,
|
|
|
|
|
) -> Result<(MainWorker, ModuleSpecifier), AnyError> {
|
2022-01-07 20:00:05 +00:00
|
|
|
//let module_loader = Rc::new(EmbeddedModuleLoader("void".to_owned()));
|
|
|
|
|
let module_loader = Rc::new(FsModuleLoader);
|
|
|
|
|
let create_web_worker_cb = Arc::new(|_| {
|
|
|
|
|
todo!("Web workers are not supported in the example");
|
|
|
|
|
});
|
2022-03-21 20:07:19 +00:00
|
|
|
let web_worker_preload_module_cb = Arc::new(|_| {
|
|
|
|
|
todo!("Web workers are not supported in the example");
|
|
|
|
|
});
|
2022-01-07 20:00:05 +00:00
|
|
|
|
|
|
|
|
let options = WorkerOptions {
|
|
|
|
|
bootstrap: BootstrapOptions {
|
|
|
|
|
args: vec![],
|
|
|
|
|
cpu_count: 1,
|
|
|
|
|
debug_flag: false,
|
|
|
|
|
enable_testing_features: false,
|
|
|
|
|
location: None,
|
|
|
|
|
no_color: false,
|
2023-01-19 17:23:38 +00:00
|
|
|
is_tty: false,
|
2022-03-21 20:07:19 +00:00
|
|
|
runtime_version: "0.50.0".to_string(),
|
2022-01-07 20:00:05 +00:00
|
|
|
ts_version: "2.0.0".to_string(),
|
|
|
|
|
unstable: false,
|
2023-01-19 17:23:38 +00:00
|
|
|
user_agent: "starmelon".to_string(),
|
2022-01-07 20:00:05 +00:00
|
|
|
},
|
2022-03-21 20:07:19 +00:00
|
|
|
extensions: extensions,
|
2022-01-07 20:00:05 +00:00
|
|
|
unsafely_ignore_certificate_errors: None,
|
|
|
|
|
root_cert_store: None,
|
|
|
|
|
seed: None,
|
|
|
|
|
module_loader,
|
|
|
|
|
create_web_worker_cb,
|
2022-03-21 20:07:19 +00:00
|
|
|
web_worker_preload_module_cb,
|
2023-01-19 17:23:38 +00:00
|
|
|
format_js_error_fn: None,
|
|
|
|
|
source_map_getter: None,
|
2022-01-07 20:00:05 +00:00
|
|
|
maybe_inspector_server: None,
|
|
|
|
|
should_break_on_first_statement: false,
|
|
|
|
|
get_error_class_fn: Some(&get_error_class_name),
|
|
|
|
|
origin_storage_dir: None,
|
|
|
|
|
blob_store: BlobStore::default(),
|
|
|
|
|
broadcast_channel: InMemoryBroadcastChannel::default(),
|
|
|
|
|
shared_array_buffer_store: None,
|
|
|
|
|
compiled_wasm_module_store: None,
|
2023-01-19 17:23:38 +00:00
|
|
|
stdio: deno_runtime::ops::io::Stdio::default(),
|
2022-01-07 20:00:05 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let main_module = deno_core::resolve_path(path_str)?;
|
|
|
|
|
// note: resolve_url is just calling url::Url::parse and mapping the error type
|
|
|
|
|
// let main_module = resolve_url(SPECIFIER)?;
|
|
|
|
|
let permissions = Permissions::allow_all();
|
|
|
|
|
|
|
|
|
|
let worker = MainWorker::from_options(main_module.clone(), permissions, options);
|
|
|
|
|
//worker.bootstrap(&options);
|
|
|
|
|
|
|
|
|
|
Ok((worker, main_module))
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-11 18:15:16 +00:00
|
|
|
pub async fn xyz<F>(
|
2022-01-07 20:00:05 +00:00
|
|
|
mut worker: MainWorker,
|
|
|
|
|
main_module: ModuleSpecifier,
|
2022-01-11 18:15:16 +00:00
|
|
|
foo: F,
|
|
|
|
|
) -> Result<(), InterpreterError>
|
|
|
|
|
where
|
2022-03-21 20:07:19 +00:00
|
|
|
F: FnOnce(deno_core::v8::HandleScope) -> Result<(), InterpreterError>,
|
2022-01-11 18:15:16 +00:00
|
|
|
{
|
2022-01-07 20:00:05 +00:00
|
|
|
let wait_for_inspector = false;
|
|
|
|
|
// step 10 load the module into our v8 isolate
|
|
|
|
|
worker
|
|
|
|
|
.execute_main_module(&main_module)
|
|
|
|
|
.instrument(info_span!("execute main module"))
|
|
|
|
|
.await?;
|
|
|
|
|
worker
|
|
|
|
|
.run_event_loop(wait_for_inspector)
|
|
|
|
|
.instrument(info_span!("run v8 event loop"))
|
|
|
|
|
.await?;
|
|
|
|
|
|
2022-01-11 18:15:16 +00:00
|
|
|
let scope_outer = worker.js_runtime.handle_scope();
|
2022-01-07 20:00:05 +00:00
|
|
|
|
2022-01-11 18:15:16 +00:00
|
|
|
foo(scope_outer)?;
|
2022-01-07 20:00:05 +00:00
|
|
|
|
|
|
|
|
worker
|
|
|
|
|
.run_event_loop(wait_for_inspector)
|
|
|
|
|
.instrument(info_span!("run v8 event loop"))
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SPECIFIER: &str = "file://$deno$/bundle.js";
|
|
|
|
|
|
|
|
|
|
struct EmbeddedModuleLoader(String);
|
|
|
|
|
|
|
|
|
|
impl ModuleLoader for EmbeddedModuleLoader {
|
|
|
|
|
fn resolve(
|
|
|
|
|
&self,
|
|
|
|
|
specifier: &str,
|
|
|
|
|
_referrer: &str,
|
|
|
|
|
_is_main: bool,
|
|
|
|
|
) -> Result<ModuleSpecifier, AnyError> {
|
|
|
|
|
if let Ok(module_specifier) = resolve_url(specifier) {
|
|
|
|
|
//if get_source_from_data_url(&module_specifier).is_ok()
|
|
|
|
|
// || specifier == SPECIFIER
|
|
|
|
|
//{
|
|
|
|
|
return Ok(module_specifier);
|
|
|
|
|
//}
|
|
|
|
|
}
|
|
|
|
|
Err(type_error(
|
|
|
|
|
"Self-contained binaries don't support module loading",
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn load(
|
|
|
|
|
&self,
|
|
|
|
|
module_specifier: &ModuleSpecifier,
|
|
|
|
|
_maybe_referrer: Option<ModuleSpecifier>,
|
|
|
|
|
_is_dyn_import: bool,
|
|
|
|
|
) -> Pin<Box<deno_core::ModuleSourceFuture>> {
|
|
|
|
|
let module_specifier = module_specifier.clone();
|
|
|
|
|
//let is_data_uri = get_source_from_data_url(&module_specifier).ok();
|
|
|
|
|
//let code = if let Some((ref source, _)) = is_data_uri {
|
|
|
|
|
// source.to_string()
|
|
|
|
|
//} else {
|
2023-01-19 17:23:38 +00:00
|
|
|
let code: Box<[u8]> = self.0.as_bytes().into();
|
2022-01-07 20:00:05 +00:00
|
|
|
//};
|
|
|
|
|
async move {
|
|
|
|
|
//if is_data_uri.is_none() && module_specifier.to_string() != SPECIFIER {
|
|
|
|
|
// return Err(type_error(
|
|
|
|
|
// "Self-contained binaries don't support module loading",
|
|
|
|
|
// ));
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
Ok(deno_core::ModuleSource {
|
|
|
|
|
code,
|
2022-03-21 20:07:19 +00:00
|
|
|
module_type: deno_core::ModuleType::JavaScript,
|
2022-01-07 20:00:05 +00:00
|
|
|
module_url_specified: module_specifier.to_string(),
|
|
|
|
|
module_url_found: module_specifier.to_string(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
.boxed_local()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|