diff --git a/Cargo.toml b/Cargo.toml index d233548..d5edf60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,49 +5,46 @@ edition = "2018" # I decided to use os_pipe becaues I want to pipe stdout of a subprocess into # stderr in real time. I want the output of the process to stderr and stdout -# show up in the original order. I looked an os_pipe uses some unsafe code to +# show up in the original order. I looked and os_pipe uses some unsafe code to # duplicate file descriptors. I am unfamiliar with the correct way of sharing # the fds. Therefore I am going to trust that their unsafe code is necessary. # Then it makes sense to use a battle tested unsafe code rather than implement # it myself. [dependencies] -ahash = "0.7" +ahash = "0.8" +clap = { version = "4.5", features = ["derive"] } elmi = { path = "../../../infra/rust-elmi", features = [ "genco" ] } naive-wadler-prettier= { path = "../../../infra/redwood-lang/compiler/naive-wadler-prettier" } os_pipe = "1.0" serde = { version = "1.0", features = [ "derive" ] } serde_json = { version ="1.0", features = [] } -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.17" # Required to generate fixture Elm files genco-extra = { path = "../../../infra/genco-extra" } - - # All of these are required for deno's javascript runtime. We need to keep the # same versions as other projects in our cargo workspace. Multiple different # versions of rusty_v8 seem to break its build script. -deno_runtime = "0.127" +deno_runtime = "0.154" tokio = { version = "1.32", features = ["full"] } -deno_core = "0.214" -deno_web = "0.150" +deno_core = "0.272" +deno_web = "0.177" futures = "0.3" - # Required to add sql query support to interpreter. Because deno expects sync # ops to be synchronous, we have to use a second async executor to run the sqlx # functions. I read the code for oneshot # (https://github.com/faern/oneshot/commit/9aa237f185e1b65d61bf92c20350cf7bee0aa88b) # and it looks reasonable. +oneshot = { version = "0.1.6", features = ["std"]} sqlx = { version = "0.7", features = [ "sqlite", "macros", "runtime-tokio", "chrono", "json", "uuid" ] } -oneshot = "0.1.3" - # required for livetable derive macro livetable-core = { path = "../../../infra/livetable/core" } +# required for clean subcommand +walkdir = "2.4" [dev-dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e660fb --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ + +When I first created starmelon in September 2021 there was a hacky workaround +for setTimeout I had to inject into the generated javascript. Now in Nov 2023 +after upgrading deno to 0.127 I find that `globalThis.__boostrap.timers` is no +longer defined. Now the elm code seems to work without requring this hack. +However I don't have any correctness tests for starmelon. To reduce the risk of +forgetting how it was working I have included the hacks from +src/exec/scripting.rs:168 + +```rust +// I think that when I set this script to be the main module, I am skipping the +// deno/runtime/js/99_main.js script that sets up a bunch of global variables. If I +// manually add the timer related code below then setTimeout works again. +// NB. there are 706 lines of setup code that add a bunch of apis to the global window +// scope. Figure out if I need to include all of them. For example, starmelon does not need +// to perform http calls right now, but I eventually want to. +final_script.push_str("const { setTimeout } = globalThis.__bootstrap.timers;\n"); +final_script.push_str( + "Deno.core.setMacrotaskCallback(globalThis.__bootstrap.timers.handleTimerMacrotask);\n", +); +final_script.push_str("globalThis.setTimeout = setTimeout;\n"); +``` + +Somewhere between `deno_runtime` version 0.127 and 0.147 they decided to remove +`deno_core::op` macro and replace it with `deno_core::op2`. As far as I can +tell, the `op2` macro offers greater control of how values are passed between +Rust and JavaScript. + +In Jan 2024 they moved operations to a virtual module +`import { op_example } from "ext:core/ops`. You can only access this virtual +module in the bootstrap ESM scripts of an `deno_core::Extension`. Consequently +what I have done is write a bootstrap script that imports the ops and reassigns +them as properties of `globalThis.Extension` object. All of my extensions are +merged onto the same object. It appears the Deno.core is deleted by the time +the `deno_runtime::worker::MainWorker` runs the main module. +`Deno[Deno.internal].core.ops` still exists but does not contain the ops our +Extensions defined. + +An aside is that if you construct a JsRuntime directly and add Extensions then +those ops will show up on `Deno.core.ops`. But they will not be enumerable +properties, so you will have to use `Object.getOwnPropertyNames(Deno.core.ops)` +to visually confirm the ops are there. My current understanding in April 2024 +is that MainWorker includes a JsRuntime, but then also applies all of the +`deno_runtime` extensions that make the JS enviroment feature comparable with +Node. For example setTimeout does not exist in a new JsRuntime but does exist +in a new MainWorker. + +You can find some of the extensions that Deno provides in +[https://github.com/denoland/deno/tree/main/runtime/js] diff --git a/always-rebuild.do b/always-rebuild.do deleted file mode 100644 index 06fcfe0..0000000 --- a/always-rebuild.do +++ /dev/null @@ -1,2 +0,0 @@ -redo-always -redo-ifchange release diff --git a/examples/sqlite-integration/README.md b/examples/sqlite-integration/README.md new file mode 100644 index 0000000..fd4f790 --- /dev/null +++ b/examples/sqlite-integration/README.md @@ -0,0 +1 @@ +./starmelon exec --sqlite example.sqlite src/ShowDatabaseSchema.elm main --output=schema.html diff --git a/examples/sqlite-integration/src/ShowDatabaseSchema.elm b/examples/sqlite-integration/src/ShowDatabaseSchema.elm index 5a0ac46..37fb0db 100644 --- a/examples/sqlite-integration/src/ShowDatabaseSchema.elm +++ b/examples/sqlite-integration/src/ShowDatabaseSchema.elm @@ -11,7 +11,7 @@ main = let query = fetch - "select json_object('type', type, 'name', name, 'sql', sql) from sqlite_master WHERE type = 'table'" + "select type, name, sql from sqlite_master WHERE type = 'table'" [] (Json.Decode.map3 (\kind name sql-> diff --git a/release.do b/release.do index 8ce97db..a8f4696 100644 --- a/release.do +++ b/release.do @@ -1,4 +1,4 @@ redo-ifchange Cargo.toml find src/ -type f | xargs redo-ifchange -cargo build --release +cargo build --color=always --release diff --git a/src/debug.rs b/src/debug.rs new file mode 100644 index 0000000..de5a4ed --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,96 @@ +use deno_core::{Extension, OpState, op2, extension}; +use elm_project_utils::{setup_generator_project, ElmResult}; +use os_pipe::dup_stderr; +use sqlx::sqlite::SqlitePool; +use std::{ + borrow::Cow, + cell::RefCell, + convert::TryFrom, + fs, + io::{self, Read, Write}, + path::PathBuf, + process::{Command, Stdio}, + sync::Arc, +}; +use tracing::info_span; + +use crate::exec::{fixtures, runtime}; +use crate::reporting::{CompilerError, InterpreterError, Problem, TypeError}; + +#[deno_core::op2(fast)] +fn op_starmelon_example( + state: &mut OpState, + #[string] msg: String, +) -> Result<(), deno_core::error::AnyError> { + eprintln!("got a String message from v8 runtime {:?}", &msg); + Ok(()) +} + +#[op2(fast)] +fn op_hello(#[string] text: &str) { + println!("Hello {} from an op!", text); +} + +extension!( + debug_ext, + ops = [op_hello], + esm_entry_point = "ext:debug_ext/debug_bootstrap.js", + esm = [dir "src", "debug_bootstrap.js"], + docs = " small example demonstrating op2 usage.", "Contains one op." +); + +pub(crate) fn run(javascript_path: Option) -> Result<(), Problem> { + let final_path: PathBuf = if let Some(ref path) = javascript_path { + std::fs::canonicalize(path).unwrap() + //std::fs::read_to_string(&path) + // .map_err(|io_err| CompilerError::ReadInputFailed(io_err, "stdout".into()))? + } else { + return Ok(()); + //"console.log('hello world')".to_owned() + }; + + // Create a tokio runtime before registering ops so we can block on futures inside sync ops + let span = info_span!("create tokio runtime"); + let timing_guard = span.enter(); + + let sys = tokio::runtime::Builder::new_current_thread() + // The default number of additional threads for running blocking FnOnce is 512. + .max_blocking_threads(1) + .enable_all() + .build() + .unwrap(); + drop(timing_guard); + + // step 10 create a v8 isolate. We need to register a different callback depending on + // the output type (string, or bytes) + + //let mailbox: Arc, String>>>> = Arc::new(RefCell::new(None)); + //let mailbox_clone = Arc::clone(&mailbox); + + let mut extensions = vec![ + Extension { + ops: Cow::Owned(vec![op_starmelon_example::decl()]), + op_state_fn: Some(Box::new(move |state| { + //state.put(Arc::clone(&mailbox_clone)); + })), + ..Default::default() + }, + debug_ext::init_ops_and_esm(), + ]; + + let span = info_span!("create v8 isolate"); + let timing_guard = span.enter(); + let (worker, main_module) = runtime::setup_worker(extensions, &final_path.to_string_lossy()) + .map_err(|err| InterpreterError::EventLoop(err))?; + drop(timing_guard); + + let entrypoint = + move |mut scope: deno_core::v8::HandleScope| -> Result<(), InterpreterError> { Ok(()) }; + + let span = info_span!("eval javascript"); + let timing_guard = span.enter(); + sys.block_on(async move { runtime::xyz(worker, main_module, entrypoint).await })?; + drop(timing_guard); + + Ok(()) +} diff --git a/src/debug_bootstrap.js b/src/debug_bootstrap.js new file mode 100644 index 0000000..9461acb --- /dev/null +++ b/src/debug_bootstrap.js @@ -0,0 +1,7 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { op_hello } from "ext:core/ops"; +function hello() { + op_hello("world"); +} + +globalThis.Extension = { hello }; diff --git a/src/derive/mod.rs b/src/derive/mod.rs index 6ed64d3..5e3b8a8 100644 --- a/src/derive/mod.rs +++ b/src/derive/mod.rs @@ -180,8 +180,8 @@ fn resolve_table_argument(tipe: &elmi::Type) -> Result { //ColumnType::MaybeFloat => Maybe Float, //ColumnType::Double => Float, //ColumnType::MaybeDouble => Maybe Float, -//ColumnType::Timestamp => #(posix_type.clone()), -//ColumnType::MaybeTimestamp => Maybe #(posix_type.clone()), +//ColumnType::Timestamp => $(posix_type.clone()), +//ColumnType::MaybeTimestamp => Maybe $(posix_type.clone()), //ColumnType::ExactlyOneAttachment => LiveTable.Attachment, //ColumnType::MaybeAttachment => Maybe LiveTable.Attachment, //ColumnType::ListAttachment => List LiveTable.Attachment, diff --git a/src/elm.rs b/src/elm.rs index 4866c6a..2d750eb 100644 --- a/src/elm.rs +++ b/src/elm.rs @@ -30,6 +30,7 @@ pub fn make(file: &Path, debug: bool, verbosity: u64) -> Result<(), Problem> { } } Err(_io_err) => { + eprintln!("{:?}", _io_err); // TODO handle this one return Err(Problem::Wildcard("elm failed".into())); } diff --git a/src/exec/astrid_pages.rs b/src/exec/astrid_pages.rs index 9059412..ce7eab5 100644 --- a/src/exec/astrid_pages.rs +++ b/src/exec/astrid_pages.rs @@ -1,22 +1,24 @@ -use crate::exec::fixtures::astrid_pages::ScriptError; -use crate::exec::{fixtures, runtime}; -use crate::reporting::{CompilerError, InterpreterError, Problem}; -use deno_core::{v8, Extension, OpState}; +use deno_core::{v8, Extension, OpState, ExtensionFileSource}; use elm_project_utils::{setup_generator_project, ElmPostProcessor, ElmResult}; use os_pipe::dup_stderr; use sqlx::sqlite::SqlitePool; -use std::borrow::Cow; -use std::cell::RefCell; -use std::convert::TryFrom; -use std::fs; -use std::io; -use std::io::Write; -use std::path::PathBuf; -use std::process::{Command, Stdio}; -use std::sync::Arc; -use tokio; +use std::{ + borrow::Cow, + cell::RefCell, + convert::TryFrom, + fs, + io, + io::Write, + path::PathBuf, + process::{Command, Stdio}, + sync::Arc, +}; use tracing::info_span; +use crate::exec::fixtures::astrid_pages::ScriptError; +use crate::exec::{fixtures, runtime}; +use crate::reporting::{CompilerError, InterpreterError, Problem}; + #[derive(Debug, Copy, Clone)] pub enum OutputType { Html, @@ -156,21 +158,6 @@ pub(crate) fn run( } _ => (), } - // I think that when I set this script to be the main module, I am skipping the - // deno/runtime/js/99_main.js script that sets up a bunch of global variables. If I - // manually add the timer related code below then setTimeout works again. - // NB. there are 706 lines of setup code that add a bunch of apis to the global window - // scope. Figure out if I need to include all of them. For example, starmelon does not need - // to perform http calls right now, but I eventually want to. - buffer.push_str( - r#" - - const { setTimeout } = globalThis.__bootstrap.timers; - Deno.core.setMacrotaskCallback(globalThis.__bootstrap.timers.handleTimerMacrotask); - globalThis.setTimeout = setTimeout; - - "#, - ); buffer.push_str(&format!( "var worker = Elm.{}.init({{flags: {{ stagename: \"Atago\"}} }});\n", @@ -183,7 +170,7 @@ pub(crate) fn run( globalThis.runOnInput = function(route) { worker.ports.onRequest.send(route) }; worker.ports.onStringOutput.subscribe(function(result) { - Deno.core.opSync('op_starmelon_string_output', result); + Extension.starmelon_string_output(result); }); // Elm will send a DataView if (worker.ports.onBytesOutput) { @@ -192,7 +179,7 @@ pub(crate) fn run( const ui8 = new Uint8Array(result.a.buffer); output.a = ui8; } - Deno.core.opSync('op_starmelon_bytes_output', result) + Extension.starmelon_bytes_output(result) }); }"#, ); @@ -268,6 +255,36 @@ pub(crate) fn run( op_state_fn: Some(Box::new(move |state| { state.put(Arc::clone(&mailbox_clone)); })), + esm_files: { + const JS: &'static [ExtensionFileSource] = &[ExtensionFileSource::new( + "ext:io/bootstrap.js", + { + const STR: ::deno_core::v8::OneByteConst = ::deno_core::FastStaticString::create_external_onebyte_const( + r#" +import { op_starmelon_bytes_output, op_starmelon_string_output, op_starmelon_problem } from "ext:core/ops"; +function starmelon_bytes_output(msg) { + return op_starmelon_bytes_output(msg); +} +function starmelon_string_output(msg) { + return op_starmelon_string_output(msg); +} +function starmelon_problem(msg) { + return op_starmelon_problem(msg); +} +globalThis.Extension = Object.assign( + globalThis.Extension || {}, + { starmelon_bytes_output, starmelon_string_output, starmelon_problem } +); +"# + .as_bytes(), + ); + let s: &'static ::deno_core::v8::OneByteConst = &STR; + ::deno_core::FastStaticString::new(s) + }, + )]; + Cow::Borrowed(JS) + }, + esm_entry_point: Some("ext:io/bootstrap.js"), ..Default::default() }]; @@ -342,10 +359,10 @@ pub(crate) fn run( type OutputMailbox = Arc, ScriptError>>>>; -#[deno_core::op] +#[deno_core::op2] fn op_starmelon_problem( - state: &mut deno_core::OpState, - msg: ScriptError, + state: &mut OpState, + #[serde] msg: ScriptError, ) -> Result<(), deno_core::error::AnyError> { eprintln!("got problem from v8 runtime {:?}", &msg); let mailbox_clone = state.borrow::(); @@ -355,10 +372,10 @@ fn op_starmelon_problem( Ok(()) } -#[deno_core::op] +#[deno_core::op2] fn op_starmelon_bytes_output( state: &mut OpState, - msg: ElmResult, + #[serde] msg: ElmResult, ) -> Result<(), deno_core::error::AnyError> { let mailbox_clone = state.borrow::(); if let Ok(mut mailbox) = mailbox_clone.try_borrow_mut() { @@ -375,10 +392,10 @@ fn op_starmelon_bytes_output( Ok(()) } -#[deno_core::op] +#[deno_core::op2] fn op_starmelon_string_output( state: &mut OpState, - msg: ElmResult, + #[serde] msg: ElmResult, ) -> Result<(), deno_core::error::AnyError> { let mailbox_clone = state.borrow::(); if let Ok(mut mailbox) = mailbox_clone.try_borrow_mut() { diff --git a/src/exec/css_in_elm.rs b/src/exec/css_in_elm.rs index c3b970a..73451e0 100644 --- a/src/exec/css_in_elm.rs +++ b/src/exec/css_in_elm.rs @@ -1,6 +1,6 @@ use crate::exec::{fixtures, runtime}; use crate::reporting::{CompilerError, InterpreterError, Problem}; -use deno_core::{Extension, OpState}; +use deno_core::{Extension, ExtensionFileSource, OpState}; use elm_project_utils::{setup_generator_project, ElmPostProcessor}; use os_pipe::dup_stderr; use serde::Deserialize; @@ -11,7 +11,6 @@ use std::io::Write; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::sync::Arc; -use tokio; use tracing::info_span; pub(crate) fn run( @@ -116,21 +115,6 @@ pub(crate) fn run( } _ => (), } - // I think that when I set this script to be the main module, I am skipping the - // deno/runtime/js/99_main.js script that sets up a bunch of global variables. If I - // manually add the timer related code below then setTimeout works again. - // NB. there are 706 lines of setup code that add a bunch of apis to the global window - // scope. Figure out if I need to include all of them. For example, starmelon does not need - // to perform http calls right now, but I eventually want to. - buffer.push_str( - r#" - - const { setTimeout } = globalThis.__bootstrap.timers; - Deno.core.setMacrotaskCallback(globalThis.__bootstrap.timers.handleTimerMacrotask); - globalThis.setTimeout = setTimeout; - - "#, - ); buffer.push_str(&format!("var worker = Elm.{}.init();\n", &gen_module_name)); // add a shortcut for invoking the function so I don't have to traverse so many object @@ -139,7 +123,7 @@ pub(crate) fn run( r#" if (worker.ports.onFilesOutput) { worker.ports.onFilesOutput.subscribe(function(result){ - Deno.core.opSync('op_starmelon_elm_css_files_output', result) + Extension.starmelon_elm_css_files_output(result) }); }"#, ); @@ -180,6 +164,29 @@ pub(crate) fn run( op_state_fn: Some(Box::new(move |state| { state.put(Arc::clone(&mailbox_clone)); })), + esm_files: { + const JS: &'static [ExtensionFileSource] = + &[ExtensionFileSource::new("ext:io/bootstrap.js", { + const STR: ::deno_core::v8::OneByteConst = + ::deno_core::FastStaticString::create_external_onebyte_const( + r#" +import { op_starmelon_elm_css_files_output } from "ext:core/ops"; +function starmelon_elm_css_files_output(msg) { + return op_starmelon_elm_css_files_output(msg); +} +globalThis.Extension = Object.assign( + globalThis.Extension || {}, + { starmelon_elm_css_files_output } +); +"# + .as_bytes(), + ); + let s: &'static ::deno_core::v8::OneByteConst = &STR; + ::deno_core::FastStaticString::new(s) + })]; + Cow::Borrowed(JS) + }, + esm_entry_point: Some("ext:io/bootstrap.js"), ..Default::default() }]; @@ -241,10 +248,10 @@ struct FileDefinition { success: bool, } -#[deno_core::op] +#[deno_core::op2] fn op_starmelon_elm_css_files_output( state: &mut OpState, - msg: Vec, + #[serde] msg: Vec, ) -> Result<(), deno_core::error::AnyError> { let mailbox_clone = state.borrow::(); if let Ok(mut mailbox) = mailbox_clone.try_borrow_mut() { diff --git a/src/exec/fixtures/astrid_pages.rs b/src/exec/fixtures/astrid_pages.rs index 2100707..6dbe57d 100644 --- a/src/exec/fixtures/astrid_pages.rs +++ b/src/exec/fixtures/astrid_pages.rs @@ -32,33 +32,33 @@ pub(crate) fn generate( import Platform -- START CUSTOMIZED PART - import #(flags_module) exposing (Flags) - #(for (target_module, _) in target_modules.iter() => - import #(target_module) - # + import $(flags_module) exposing (Flags) + $(for (target_module, _) in target_modules.iter() => + import $(target_module) + $ ) dispatch route flags = case route of - #(for (target_module, output_type) in target_modules.iter() => - #(" ")#(quoted(target_module)) -> - #( match output_type { + $(for (target_module, output_type) in target_modules.iter() => + $(" ")$(quoted(target_module)) -> + $( match output_type { OutputType::String => { - #(" ")evalRoute (onStringOutput << encodeString) flags #(target_module).route + $(" ")evalRoute (onStringOutput << encodeString) flags $(target_module).route } OutputType::Value => { - #(" ")evalRoute (onStringOutput << encodeJson) flags #(target_module).route + $(" ")evalRoute (onStringOutput << encodeJson) flags $(target_module).route } OutputType::Bytes => { - #(" ")evalRoute (onBytesOutput << encodeBytes) flags #(target_module).route + $(" ")evalRoute (onBytesOutput << encodeBytes) flags $(target_module).route } OutputType::Html => { - #(" ")evalRoute (onStringOutput << encodeHtml) flags #(target_module).route + $(" ")evalRoute (onStringOutput << encodeHtml) flags $(target_module).route } }) - # + $ ) - # + $ _ -> onStringOutput (encodeFailure NotFound) @@ -198,7 +198,7 @@ pub(crate) fn generate( in case D.decodeString - (decodeElmHtml ( #("\\")taggers eventHandler -> D.succeed ())) + (decodeElmHtml ( $("\\")taggers eventHandler -> D.succeed ())) (asJsonString node) of Ok elmHtml -> diff --git a/src/exec/fixtures/css_in_elm.rs b/src/exec/fixtures/css_in_elm.rs index dc1b764..f0358b1 100644 --- a/src/exec/fixtures/css_in_elm.rs +++ b/src/exec/fixtures/css_in_elm.rs @@ -8,9 +8,9 @@ pub(crate) fn generate(source_checksum: u64, entrypoint: &elmi::Global) -> (Stri import Stylesheets -- START CUSTOMIZED PART - import #(&entrypoint.0.module) + import $(&entrypoint.0.module) - entrypoint = #(&entrypoint.0.module).#(&entrypoint.1) + entrypoint = $(&entrypoint.0.module).$(&entrypoint.1) -- END CUSTOMIZED PART -- MAIN @@ -39,7 +39,7 @@ pub(crate) fn generate(source_checksum: u64, entrypoint: &elmi::Global) -> (Stri structure = Css.File.toFileStructure <| List.map - (#("\\")(fileName, stylesheets) -> + ($("\\")(fileName, stylesheets) -> (fileName, Css.File.compile stylesheets) ) entrypoint diff --git a/src/exec/fixtures/scripting.rs b/src/exec/fixtures/scripting.rs index 4399953..b981d15 100644 --- a/src/exec/fixtures/scripting.rs +++ b/src/exec/fixtures/scripting.rs @@ -180,14 +180,14 @@ decodeString value = encodeFailure : String -> E.Value encodeFailure msg = E.object - [ ("ctor", E.string "Err") + [ ("$", E.string "Err") , ("a", E.string msg) ] encodeSuccess : E.Value -> E.Value encodeSuccess value = E.object - [ ("ctor", E.string "Ok") + [ ("$", E.string "Ok") , ("a", value) ] diff --git a/src/exec/fixtures/sql-client-integration.js b/src/exec/fixtures/sql-client-integration.js index d61ff2f..e610e1f 100644 --- a/src/exec/fixtures/sql-client-integration.js +++ b/src/exec/fixtures/sql-client-integration.js @@ -1,18 +1,18 @@ -function (query) { +var $author$project$Astrid$Query$execute = function (query) { return $author$project$Astrid$Query$dummyExecute; }; // CORE QUERIES function __Debug_print(object) { - Deno.core.print(JSON.stringify(object)); - Deno.core.print("\n"); + Deno[Deno.internal].core.print(JSON.stringify(object)); + Deno[Deno.internal].core.print("\n"); } function __Debug_print_slots(values) { var len = values.length; for (var i = 0; i < len; i++) { - Deno.core.print([" ", i, ": ", JSON.stringify(values[i]), "\n"].join("")); + Deno[Deno.internal].core.print([" ", i, ": ", JSON.stringify(values[i]), "\n"].join("")); } } @@ -129,6 +129,7 @@ function _Query_runDecoder(decoder, sql, xs) return $elm$core$Result$Ok($elm$core$Maybe$Nothing); } var result = _Json_runOnString.f(decoder.a, xs[0]); + console.log("result of decoding string", result) if (!$elm$core$Result$isOk(result)) { @@ -207,6 +208,8 @@ function _Query_runDecoder(decoder, sql, xs) var _Query_execute = function(query) { + try { + console.log("inside _Query_execute") // queries: Array (Int, Query a) // values: Array (Maybe a) // callbacks: Array (Int, Fn: * -> a) @@ -262,8 +265,8 @@ var _Query_execute = function(query) } if (statements.length > 0) { - var queryResult = Deno.core.opSync( - 'op_starmelon_batch_queries', + console.log("Extension", Extension) + var queryResult = Extension.starmelon_batch_queries( statements, ); // I am assuming here that the Rust code is serializing the same @@ -335,6 +338,9 @@ var _Query_execute = function(query) return $elm$core$Result$Ok(values.pop().a) } } + } catch(err) { + console.error("Failure:", err) + } }; var $author$project$Astrid$Query$execute = _Query_execute; diff --git a/src/exec/fixtures/sqlite.rs b/src/exec/fixtures/sqlite.rs index be66f9f..5e619fb 100644 --- a/src/exec/fixtures/sqlite.rs +++ b/src/exec/fixtures/sqlite.rs @@ -1,5 +1,5 @@ use deno_core::futures::StreamExt; -use deno_core::{Extension, Op, OpState}; +use deno_core::{Extension, ExtensionFileSource, Op, OpState}; use elm_project_utils::ElmResult; use serde::{Deserialize, Serialize}; use serde_json::{value::Map, value::Number, Value}; @@ -7,7 +7,6 @@ use sqlx::{sqlite::SqlitePool, Column, Row, TypeInfo, ValueRef}; use std::borrow::Cow; use std::thread::JoinHandle; use std::time::Instant; -use tokio; use tracing::{info_span, Instrument}; type SQLWorkerMailbox = std::sync::mpsc::Sender<( @@ -113,20 +112,45 @@ pub(crate) fn init(db_pool: SqlitePool) -> Result<(Extension, JoinHandle<()>), ( }); let worker_mailbox_clone = worker_mailbox.clone(); let extension = Extension { + name: "sqlite", ops: Cow::Owned(vec![op_starmelon_batch_queries::DECL]), op_state_fn: Some(Box::new(move |state| { state.put(worker_mailbox_clone.clone()); })), + esm_files: { + const JS: &'static [ExtensionFileSource] = + &[ExtensionFileSource::new("ext:sqlite/bootstrap.js", { + const STR: ::deno_core::v8::OneByteConst = + ::deno_core::FastStaticString::create_external_onebyte_const( + r#" +import { op_starmelon_batch_queries } from "ext:core/ops"; +function starmelon_batch_queries(queries) { + return op_starmelon_batch_queries(queries); +} +globalThis.Extension = Object.assign(globalThis.Extension || {}, { starmelon_batch_queries }); +"# + .as_bytes(), + ); + let s: &'static ::deno_core::v8::OneByteConst = &STR; + ::deno_core::FastStaticString::new(s) + })]; + Cow::Borrowed(JS) + }, + esm_entry_point: Some("ext:sqlite/bootstrap.js"), + enabled: true, ..Default::default() }; Ok((extension, sql_worker_thread)) } -#[deno_core::op] +deno_core::extension!(hello_runtime, ops = [op_starmelon_batch_queries]); + +#[deno_core::op2] +#[serde] fn op_starmelon_batch_queries( state: &mut OpState, - queries: Vec<(bool, String, Vec)>, + #[serde] queries: Vec<(bool, String, Vec)>, ) -> Result>, AstridQueryError>, deno_core::error::AnyError> { let worker_mailbox_clone = state.borrow::(); diff --git a/src/exec/mod.rs b/src/exec/mod.rs index b99ba94..b1d491e 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -8,7 +8,7 @@ use tracing::info_span; mod astrid_pages; mod css_in_elm; -mod fixtures; +pub(crate) mod fixtures; mod scripting; pub(crate) fn exec( @@ -169,55 +169,49 @@ fn is_css_in_elm_stylesheet(tipe: &elmi::Type) -> bool { false } -mod runtime { +pub(crate) mod runtime { use crate::reporting::InterpreterError; - use deno_core::error::{type_error, AnyError}; - use deno_core::futures::FutureExt; - use deno_core::{resolve_url, Extension, FsModuleLoader, ModuleLoader, ModuleSpecifier}; - use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; + use deno_core::error::AnyError; + use deno_core::{Extension, FsModuleLoader, ModuleSpecifier}; use deno_runtime::permissions::PermissionsContainer; use deno_runtime::worker::MainWorker; use deno_runtime::worker::WorkerOptions; - use deno_runtime::BootstrapOptions; - use deno_web::BlobStore; - use std::pin::Pin; + //use deno_runtime::BootstrapOptions; 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") - } - pub fn setup_worker( extensions: Vec, path_str: &str, ) -> Result<(MainWorker, ModuleSpecifier), AnyError> { - let module_loader = Rc::new(FsModuleLoader); + //let module_loader = Rc::new(FsModuleLoader); let options = WorkerOptions { - bootstrap: BootstrapOptions { - args: vec![], - cpu_count: 1, - enable_testing_features: false, - location: None, - no_color: false, - is_tty: false, - runtime_version: "0.127.0".to_string(), - ts_version: "2.0.0".to_string(), - unstable: false, - user_agent: "starmelon".to_string(), - ..Default::default() - }, + //module_loader: Rc::new(FsModuleLoader), + //bootstrap: BootstrapOptions { + // args: vec![], + // cpu_count: 1, + // enable_testing_features: false, + // location: None, + // no_color: false, + // is_tty: false, + // runtime_version: "0.127.0".to_string(), + // ts_version: "2.0.0".to_string(), + // unstable: false, + // user_agent: "starmelon".to_string(), + // ..Default::default() + //}, extensions, + //should_break_on_first_statement: false, + //should_wait_for_inspector_session: false, ..Default::default() }; let main_module = ModuleSpecifier::from_file_path(path_str) - .map_err(|_| AnyError::msg("path is not absolute"))?; + .map_err(|()| AnyError::msg("path is not absolute"))?; let permissions = PermissionsContainer::allow_all(); - let worker = MainWorker::from_options(main_module.clone(), permissions, options); + let worker = MainWorker::bootstrap_from_options(main_module.clone(), permissions, options); //worker.bootstrap(&options); Ok((worker, main_module)) @@ -231,12 +225,13 @@ mod runtime { where F: FnOnce(deno_core::v8::HandleScope) -> Result<(), InterpreterError>, { - 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?; + + let wait_for_inspector = false; worker .run_event_loop(wait_for_inspector) .instrument(info_span!("run v8 event loop")) diff --git a/src/exec/scripting.rs b/src/exec/scripting.rs index 2b6979e..d641ead 100644 --- a/src/exec/scripting.rs +++ b/src/exec/scripting.rs @@ -1,6 +1,6 @@ use crate::exec::{fixtures, runtime}; use crate::reporting::{CompilerError, InterpreterError, Problem, TypeError}; -use deno_core::{Extension, OpState}; +use deno_core::{Extension, ExtensionFileSource, OpState}; use elm_project_utils::{setup_generator_project, ElmResult}; use os_pipe::dup_stderr; use sqlx::sqlite::SqlitePool; @@ -12,7 +12,6 @@ use std::io::{self, Read, Write}; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::sync::Arc; -use tokio; use tracing::info_span; pub(crate) fn run( @@ -66,6 +65,7 @@ pub(crate) fn run( command .arg("make") + .arg("--report=json") .arg("--output") .arg(&intermediate_file) .current_dir(&generator_dir) @@ -89,10 +89,13 @@ pub(crate) fn run( match command.output() { Ok(output) => { if !output.status.success() { + eprintln!("output of elm make: failed building scripting fixture file"); return Err(CompilerError::FailedBuildingFixture.into()); } + //command.stdout(Stdio::piped()); } - Err(_) => { + Err(err) => { + eprintln!("got this kind of failure {:?}", err); return Err(CompilerError::FailedBuildingFixture.into()); } } @@ -158,9 +161,8 @@ pub(crate) fn run( // final_script.replace("var $author$project$Astrid$Query$run = ", "JSON.stringify(x)"); final_script.push_str("\n\n"); //final_script.push_str(r#" - // Deno.core.print(JSON.stringify( - // Deno.core.opSync( - // 'op_starmelon_batch_queries', + // Deno[Deno.internal].core.print(JSON.stringify( + // Extension.starmelon_batch_queries( // [ [true, "select json_object('id', id, 'foo', foo) from foobar", []] // , [false, "select json_object('id', id, 'foo', foo) from foobar", []] // ] @@ -170,17 +172,6 @@ pub(crate) fn run( } final_script.push_str("\n\n"); - // I think that when I set this script to be the main module, I am skipping the - // deno/runtime/js/99_main.js script that sets up a bunch of global variables. If I - // manually add the timer related code below then setTimeout works again. - // NB. there are 706 lines of setup code that add a bunch of apis to the global window - // scope. Figure out if I need to include all of them. For example, starmelon does not need - // to perform http calls right now, but I eventually want to. - final_script.push_str("const { setTimeout } = globalThis.__bootstrap.timers;\n"); - final_script.push_str( - "Deno.core.setMacrotaskCallback(globalThis.__bootstrap.timers.handleTimerMacrotask);\n", - ); - final_script.push_str("globalThis.setTimeout = setTimeout;\n"); final_script.push_str(&format!("var worker = Elm.{}.init();\n", &gen_module_name)); // add a shortcut for invoking the function so I don't have to traverse so many object @@ -216,11 +207,12 @@ pub(crate) fn run( final_script.push_str( r#" worker.ports.onOutput.subscribe(function(output){ + console.log("got output value") if (output.ctor === "Ok") { const json = JSON.stringify(output.a); - Deno.core.opSync('op_starmelon_string_output', output); + Extension.starmelon_string_output(output); } else { - Deno.core.opSync('op_starmelon_problem', output.a); + Extension.starmelon_problem(output.a); } }); "#, @@ -231,11 +223,12 @@ pub(crate) fn run( r#" // Elm will send a DataView worker.ports.onOutput.subscribe(function(output){ + console.log("got output bytes") if (output.ctor === "Ok") { const ui8 = new Uint8Array(output.a.buffer); - Deno.core.opSync('op_starmelon_bytes_output', ui8); + Extension.starmelon_bytes_output(ui8); } else { - Deno.core.opSync('op_starmelon_problem', output.a); + Extension.starmelon_problem(output.a); } }); "#, @@ -244,12 +237,10 @@ pub(crate) fn run( OutputType::String | OutputType::Html => { final_script.push_str( r#" - worker.ports.onOutput.subscribe(function(output){ - if (output.ctor === "Ok") { - Deno.core.opSync('op_starmelon_string_output', output.a); - } else { - Deno.core.opSync('op_starmelon_problem', output.a); - } + worker.ports.onOutput.subscribe(function outputCallback(output){ + console.log("calling ports.onOutput callback for HTML", output) + // type ElmResult = Ok string | Err string + Extension.starmelon_string_output(output); }); "#, ); @@ -268,6 +259,16 @@ pub(crate) fn run( .map_err(|io_err| CompilerError::WriteOutputFailed(io_err, final_file.clone()))?; drop(timing_guard); + // for debugging purposes we have the option to pause here + //if true { + // let mut line = String::new(); + // eprintln!( + // "Paused after writing `{:?}`. Continue ?", + // final_file.to_string_lossy() + // ); + // std::io::stdin().read_line(&mut line).unwrap(); + //} + // Create a tokio runtime before registering ops so we can block on futures inside sync ops let span = info_span!("create tokio runtime"); let timing_guard = span.enter(); @@ -286,6 +287,7 @@ pub(crate) fn run( let mailbox_clone = Arc::clone(&mailbox); let mut extensions = vec![Extension { + name: "io", ops: Cow::Owned(vec![ op_starmelon_bytes_output::decl(), op_starmelon_string_output::decl(), @@ -294,6 +296,36 @@ pub(crate) fn run( op_state_fn: Some(Box::new(move |state| { state.put(Arc::clone(&mailbox_clone)); })), + esm_files: { + const JS: &'static [ExtensionFileSource] = &[ExtensionFileSource::new( + "ext:io/bootstrap.js", + { + const STR: ::deno_core::v8::OneByteConst = ::deno_core::FastStaticString::create_external_onebyte_const( + r#" +import { op_starmelon_bytes_output, op_starmelon_string_output, op_starmelon_problem } from "ext:core/ops"; +function starmelon_bytes_output(msg) { + return op_starmelon_bytes_output(msg); +} +function starmelon_string_output(msg) { + return op_starmelon_string_output(msg); +} +function starmelon_problem(msg) { + return op_starmelon_problem(msg); +} +globalThis.Extension = Object.assign( + globalThis.Extension || {}, + { starmelon_bytes_output, starmelon_string_output, starmelon_problem } +); +"# + .as_bytes(), + ); + let s: &'static ::deno_core::v8::OneByteConst = &STR; + ::deno_core::FastStaticString::new(s) + }, + )]; + Cow::Borrowed(JS) + }, + esm_entry_point: Some("ext:io/bootstrap.js"), ..Default::default() }]; @@ -309,6 +341,7 @@ pub(crate) fn run( .unwrap(); if let Ok((extension, thread_handle)) = fixtures::sqlite::init(db_pool) { + eprintln!("started sqlite extension"); extensions.push(extension); Some(thread_handle) } else { @@ -364,7 +397,7 @@ pub(crate) fn run( } }; - let foo = move |mut scope: deno_core::v8::HandleScope| -> Result<(), InterpreterError> { + let entrypoint = move |mut scope: deno_core::v8::HandleScope| -> Result<(), InterpreterError> { let scope = &mut scope; let ctx = scope.get_current_context(); let global = ctx.global(scope); @@ -429,7 +462,7 @@ pub(crate) fn run( let span = info_span!("eval javascript"); let timing_guard = span.enter(); - sys.block_on(async move { runtime::xyz(worker, main_module, foo).await })?; + sys.block_on(async move { runtime::xyz(worker, main_module, entrypoint).await })?; drop(timing_guard); // step 13 receive the callback @@ -467,10 +500,10 @@ pub(crate) fn run( type OutputMailbox = Arc, String>>>>; -#[deno_core::op] +#[deno_core::op2(fast)] fn op_starmelon_problem( - state: &mut deno_core::OpState, - msg: String, + state: &mut OpState, + #[string] msg: String, ) -> Result<(), deno_core::error::AnyError> { eprintln!("got problem from v8 runtime {:?}", &msg); let mailbox_clone = state.borrow::(); @@ -480,10 +513,10 @@ fn op_starmelon_problem( Ok(()) } -#[deno_core::op] +#[deno_core::op2] fn op_starmelon_bytes_output( state: &mut OpState, - msg: ElmResult, + #[serde] msg: ElmResult, ) -> Result<(), deno_core::error::AnyError> { let mailbox_clone = state.borrow::(); if let Ok(mut mailbox) = mailbox_clone.try_borrow_mut() { @@ -500,10 +533,10 @@ fn op_starmelon_bytes_output( Ok(()) } -#[deno_core::op] +#[deno_core::op2] fn op_starmelon_string_output( state: &mut OpState, - msg: ElmResult, + #[serde] msg: ElmResult, ) -> Result<(), deno_core::error::AnyError> { let mailbox_clone = state.borrow::(); if let Ok(mut mailbox) = mailbox_clone.try_borrow_mut() { diff --git a/src/main.rs b/src/main.rs index 0b3fd65..70d3a36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,9 +5,10 @@ use std::hash::Hasher; use std::io::{self, Write}; use std::path::PathBuf; use std::time::Instant; -use structopt::StructOpt; use tracing::info_span; +use clap::Parser; +mod debug; mod derive; mod elm; mod exec; @@ -16,12 +17,46 @@ mod timings; mod transpile; fn main() { - let args = Arguments::from_args(); + let args = Arguments::parse(); let (span_subscriber, timing_report) = Timings::new(); tracing::subscriber::set_global_default(span_subscriber).unwrap(); match args { + Arguments::Clean => { + let result = (|| -> Result<(), crate::reporting::Problem> { + let elm_project_dir = crate::elm::find_project_root("elm.json", "./") + .map_err(crate::reporting::CompilerError::MissingElmJson)?; + for our_temp_dir in [ + elm_project_dir.join("elm-stuff").join("starmelon-ce7993"), + elm_project_dir.join("elm-stuff").join("starmelon-5d9ecc") + ] { + for entry in walkdir::WalkDir::new(our_temp_dir).contents_first(true) { + if entry.is_err() { + eprintln!("{:?}", entry); + continue; + } + let entry = entry.unwrap(); + // TODO implement an error message for failure to delete a file during clean + if entry.file_type().is_file() { + let _ = std::fs::remove_file(entry.path()); + } + if entry.file_type().is_dir() { + let _ = std::fs::remove_dir(entry.path()); + } + eprintln!("{}", entry.path().display()); + } + } + + Ok(()) + })(); + if let Err(problem) = result { + let span = info_span!("pretty print problem"); + let timing_guard = span.enter(); + eprintln!("{}", pretty(80, problem.to_doc())); + drop(timing_guard); + } + } Arguments::Exec { file, debug, @@ -35,7 +70,7 @@ fn main() { let start = Instant::now(); let span = info_span!("exec"); let timing_guard = span.enter(); - let result = exec::exec(file, debug, function, input, output, verbosity, sqlite); + let result = exec::exec(file, debug, function, input, output, verbosity as u64, sqlite); drop(timing_guard); if let Err(problem) = result { let span = info_span!("pretty print problem"); @@ -69,8 +104,9 @@ fn main() { verbosity, debug, } => { - transpile::transpile(file, debug, function, verbosity).unwrap(); + transpile::transpile(file, debug, function, verbosity.into()).unwrap(); } + Arguments::Derive(DeriveMacros::Other) => (), Arguments::Derive(DeriveMacros::LiveTable { file, debug, @@ -82,7 +118,7 @@ fn main() { let span = info_span!("derive livetable editor"); let timing_guard = span.enter(); let result = - derive::derive_livetable(file, debug, output, verbosity /*, sqlite */); + derive::derive_livetable(file, debug, output, verbosity.into() /*, sqlite */); drop(timing_guard); if let Err(problem) = result { let span = info_span!("pretty print problem"); @@ -110,6 +146,40 @@ fn main() { Instant::now() - start ); } + Arguments::Debug { javascript, timings } => { + let start = Instant::now(); + + let span = info_span!("debug javascript"); + let timing_guard = span.enter(); + let result = crate::debug::run(javascript); + + if let Err(problem) = result { + let span = info_span!("pretty print problem"); + let timing_guard = span.enter(); + eprintln!("{}", pretty(80, problem.to_doc())); + drop(timing_guard); + } + { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + handle.flush().unwrap(); + } + + if timings { + let mut report = timing_report.dump().unwrap(); + report.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap()); + + for (step, duration) in report.iter() { + eprintln!("[{:>10.3?}] {}", duration, step); + } + } + eprintln!( + "\t\x1b[1;92mFinished\x1b[0m [{}] in {:?}", + "unoptimized", + Instant::now() - start + ); + + } } } @@ -117,8 +187,9 @@ struct PortableHash(ahash::AHasher); impl PortableHash { fn new() -> Self { + use std::hash::BuildHasher; // We need constant keys to get the same checksup every time we run the program. - Self(ahash::AHasher::new_with_keys(1, 2)) + Self(ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher()) } } @@ -143,53 +214,59 @@ impl ::core::hash::Hasher for PortableHash { } } -#[derive(Debug, StructOpt)] +#[derive(Debug, clap::Parser)] enum Arguments { + Clean, Exec { - #[structopt(parse(from_os_str))] file: PathBuf, function: String, - #[structopt(long)] + #[arg(long)] debug: bool, - #[structopt(long)] + #[arg(long)] input: Option, - #[structopt(long)] + #[arg(long)] output: Option, - #[structopt(short = "v", parse(from_occurrences))] - verbosity: u64, - #[structopt(long)] + #[arg(short = 'v', action(clap::ArgAction::Count))] + verbosity: u8, + #[arg(long)] sqlite: Option, - #[structopt(long)] + #[arg(long)] timings: bool, }, Transpile { - #[structopt(parse(from_os_str))] file: PathBuf, function: String, - #[structopt(short = "v", parse(from_occurrences))] - verbosity: u64, - #[structopt(long)] + #[arg(short = 'v', action(clap::ArgAction::Count))] + verbosity: u8, + #[arg(long)] debug: bool, }, - #[structopt(name = "derive")] + #[command(name = "derive", subcommand)] Derive(DeriveMacros), + Debug { + #[arg(long = "js")] + javascript: Option, + #[arg(long)] + timings: bool, + } } -#[derive(Debug, StructOpt)] +#[derive(Debug, clap::Parser)] pub enum DeriveMacros { - #[structopt(name = "livetable")] + #[command(name = "livetable")] LiveTable { - #[structopt(parse(from_os_str))] file: PathBuf, - #[structopt(short = "v", parse(from_occurrences))] - verbosity: u64, - #[structopt(long)] + #[arg(short = 'v', action(clap::ArgAction::Count))] + verbosity: u8, + #[arg(long)] debug: bool, - #[structopt(long)] + #[arg(long)] output: Option, - #[structopt(long)] + #[arg(long)] timings: bool, }, + #[command(subcommand)] + Other, } pub mod generated { diff --git a/src/timings.rs b/src/timings.rs index 3ab297c..2163a12 100644 --- a/src/timings.rs +++ b/src/timings.rs @@ -1,8 +1,8 @@ //! This module provides a crude system for timing how long various steps of the program take to -//! execute using the tracing framework. I seek absolution for suboptimal code. For example I want -//! to throw a span around all clones and see exactly how long I spend copying data. If I see that -//! I only spend a few milliseconds overall then that could be a price worth paying for development -//! velocity. +//! execute using the tracing framework. My goal is to give myself permission to write suboptimal +//! code. For example I want to throw a span around all clones and see exactly how long I spend +//! copying data. If I see that I only spend a few milliseconds overall then that could be a price +//! worth paying for development velocity. //! //! This is quite a different use case then flame graphs. use rustc_hash::FxHashMap; @@ -16,7 +16,6 @@ use std::time::{Duration, Instant}; use tracing::span::{Attributes, Record}; use tracing::{Event, Id, Metadata}; -// // Each step will be described with a less than 100 char string. Steps will overlap. // // I am not tracking the relationship between diff --git a/src/transpile.rs b/src/transpile.rs index 51315e9..fdd402c 100644 --- a/src/transpile.rs +++ b/src/transpile.rs @@ -402,45 +402,45 @@ mod rust_codegen { body: &elmi::Expr, ) { quote_in! { *tokens => - fn #name#(if !type_variables.is_empty() => - <#(for tvar in type_variables.iter() join (, ) => - #tvar + fn $name$(if !type_variables.is_empty() => + <$(for tvar in type_variables.iter() join (, ) => + $tvar )> - )(#(for (parameter, tipe) in parameters.iter() join (, ) => - #parameter: #(ref out { codegen_type(out, tipe) }) - )) -> #(ref out { codegen_type(out, &return_type) }) { - #(ref out { codegen_expr(out, symbol_table, body) }) + )($(for (parameter, tipe) in parameters.iter() join (, ) => + $parameter: $(ref out { codegen_type(out, tipe) }) + )) -> $(ref out { codegen_type(out, &return_type) }) { + $(ref out { codegen_expr(out, symbol_table, body) }) } } } fn codegen_type(tokens: &mut rust::Tokens, tipe: &elmi::Type) { quote_in! { *tokens => - #(match tipe { + $(match tipe { elmi::Type::TLambda(a, b) => { - ( #(ref out => codegen_type(out, a) ) -> #(ref out => codegen_type(out, b) ) ) + ( $(ref out => codegen_type(out, a) ) -> $(ref out => codegen_type(out, b) ) ) } elmi::Type::TVar(elmi::Name(variable)) => { - #variable + $variable }, elmi::Type::TType(module_name, name, args) if module_name == "elm/core/String" && name == "String" && args.is_empty() => { String } elmi::Type::TType(home, name, args) if args.is_empty() => { - #(ref out => codegen_name_from_global(out, home, name)) + $(ref out => codegen_name_from_global(out, home, name)) } elmi::Type::TType(home, name, args) => { - #(ref out => codegen_name_from_global(out, home, name))<#(for arg in args join(, ) => - #(ref out => codegen_type(out, arg)) + $(ref out => codegen_name_from_global(out, home, name))<$(for arg in args join(, ) => + $(ref out => codegen_type(out, arg)) )> } // // Might be a primitive type - // #(if module_name == "elm/core/String" && name == "String" => String) - // #(if module_name == "elm/core/Basics" && name == "Int" => i64) - // #(if module_name == "elm/core/Basics" && name == "Float" => f64) - // #(if module_name == "elm/core/Basics" && name == "Bool" => bool) - // #(if module_name == "elm/core/Maybe" && name == "Maybe" => Option) - // #(if module_name == "elm/bytes/Bytes" && name == "Bytes" => Vec) + // $(if module_name == "elm/core/String" && name == "String" => String) + // $(if module_name == "elm/core/Basics" && name == "Int" => i64) + // $(if module_name == "elm/core/Basics" && name == "Float" => f64) + // $(if module_name == "elm/core/Basics" && name == "Bool" => bool) + // $(if module_name == "elm/core/Maybe" && name == "Maybe" => Option) + // $(if module_name == "elm/bytes/Bytes" && name == "Bytes" => Vec) //} //elmi::Type::TType(_, _, _) => Err(TypeError::CantEvalCustomType), //elmi::Type::TRecord(_, _) => Err(TypeError::CantEvalRecord), @@ -479,7 +479,7 @@ mod rust_codegen { name: &elmi::Name, ) { quote_in! { *tokens => - #(ref out => codegen_home_to_builder(out, home) )__#name + $(ref out => codegen_home_to_builder(out, home) )__$name } } @@ -490,7 +490,7 @@ mod rust_codegen { } = global; quote_in! { *tokens => - _#(author.replace("-", "_"))_#(project.replace("-", "_"))__#(home.0.replace(".", "_")) + _$(author.replace("-", "_"))_$(project.replace("-", "_"))__$(home.0.replace(".", "_")) } } @@ -498,18 +498,18 @@ mod rust_codegen { match expr { elmi::Expr::Bool(true) => quote_in! { *tokens => true }, elmi::Expr::Bool(false) => quote_in! { *tokens => false }, - elmi::Expr::Chr(c) => quote_in! { *tokens => #("'")#c#("'") }, - elmi::Expr::Str(s) => quote_in! { *tokens => #(quoted(s)) }, - elmi::Expr::Int(x) => quote_in! { *tokens => #(x.to_string()) }, - elmi::Expr::Float(x) => quote_in! { *tokens => #(x.to_string()) }, + elmi::Expr::Chr(c) => quote_in! { *tokens => $("'")$c$("'") }, + elmi::Expr::Str(s) => quote_in! { *tokens => $(quoted(s)) }, + elmi::Expr::Int(x) => quote_in! { *tokens => $(x.to_string()) }, + elmi::Expr::Float(x) => quote_in! { *tokens => $(x.to_string()) }, elmi::Expr::VarLocal(name) => { quote_in! { *tokens => - #name + $name } } elmi::Expr::VarGlobal(elmi::Global(home, name)) => { quote_in! { *tokens => - #(ref out => codegen_name_from_global(out, home, name)) + $(ref out => codegen_name_from_global(out, home, name)) } } //elmi::Expr::VarEnum(Global, IndexZeroBased), @@ -523,7 +523,7 @@ mod rust_codegen { } else { quote_in! { *tokens => &[ - #(for x in xs join (,#) => #(ref out => codegen_expr(out, symbol_table, x) ) ) + $(for x in xs join (,$) => $(ref out => codegen_expr(out, symbol_table, x) ) ) ] } } @@ -531,7 +531,7 @@ mod rust_codegen { elmi::Expr::Function(_parameters, _body) => { quote_in! { *tokens => "i don't know how to code gen a function expression" - //#(for elmi::Name(ref parameter) in parameters.iter() join (, ) => + //$(for elmi::Name(ref parameter) in parameters.iter() join (, ) => //) } } @@ -548,11 +548,11 @@ mod rust_codegen { ))); } quote_in! { *tokens => - Box::new(| #(for arg in closure_args.iter() join (, ) => #(ref out => codegen_expr(out, symbol_table, arg))) | { - #(ref out => { + Box::new(| $(for arg in closure_args.iter() join (, ) => $(ref out => codegen_expr(out, symbol_table, arg))) | { + $(ref out => { codegen_name_from_global(out, home, name) })( - #(for arg in args.iter().chain(closure_args.iter()) join (,#) => #(ref out => + $(for arg in args.iter().chain(closure_args.iter()) join (,$) => $(ref out => codegen_expr(out, symbol_table, arg) ) ) ) @@ -560,10 +560,10 @@ mod rust_codegen { } } else { quote_in! { *tokens => - #(ref out => { + $(ref out => { codegen_name_from_global(out, home, name) })( - #(for arg in args join (,#) => #(ref out => + $(for arg in args join (,$) => $(ref out => codegen_expr(out, symbol_table, arg) ) ) ) @@ -581,8 +581,8 @@ mod rust_codegen { } elmi::Expr::VarLocal(name) => { quote_in! { *tokens => - #name( - #(for arg in args join (,#) => #(ref out => + $name( + $(for arg in args join (,$) => $(ref out => codegen_expr(out, symbol_table, arg) ) ) ) @@ -593,7 +593,7 @@ mod rust_codegen { // TODO write a function that can take an expression and return the arity using // the symbol table from the bottom up. quote_in! { *tokens => - #(format!("{:?}", fexpr)) + $(format!("{:?}", fexpr)) } //panic!("calling an expression not yet supported"); } @@ -602,19 +602,19 @@ mod rust_codegen { //elmi::Expr::TailCall(Name, Vec<(Name, Expr)>), elmi::Expr::If(branches, _final_branch) => { quote_in! { *tokens => - #(for (condition, expr) in branches join (##("} else")) => - if #(ref out => codegen_expr(out, symbol_table, condition)) #("{") - #(ref out => codegen_expr(out, symbol_table, expr)) - ) #("} else {") - #(ref out => codegen_expr(out, symbol_table, expr)) - #("}") + $(for (condition, expr) in branches join ($$("} else")) => + if $(ref out => codegen_expr(out, symbol_table, condition)) $("{") + $(ref out => codegen_expr(out, symbol_table, expr)) + ) $("} else {") + $(ref out => codegen_expr(out, symbol_table, expr)) + $("}") } } elmi::Expr::Let(def, expr) => { quote_in! { *tokens => - #(ref out => codegen_def(out, symbol_table, def)) - # - #(ref out => codegen_expr(out, symbol_table, expr)) + $(ref out => codegen_def(out, symbol_table, def)) + $ + $(ref out => codegen_expr(out, symbol_table, expr)) } } //elmi::Expr::Destruct(Destructor, Box), @@ -624,7 +624,7 @@ mod rust_codegen { } elmi::Expr::Accessor(name) => { quote_in! { *tokens => - Box::new(|_v| { _v.#name }) + Box::new(|_v| { _v.$name }) } } //elmi::Expr::Access(Box, Name), @@ -633,16 +633,16 @@ mod rust_codegen { elmi::Expr::Unit => (), elmi::Expr::Tuple(a, b, None) => { quote_in! { *tokens => - ( #(ref out => codegen_expr(out, symbol_table, a) ), #(ref out => codegen_expr(out, symbol_table, b) ) ) + ( $(ref out => codegen_expr(out, symbol_table, a) ), $(ref out => codegen_expr(out, symbol_table, b) ) ) } } elmi::Expr::Tuple(a, b, Some(c)) => { quote_in! { *tokens => - ( #(ref out => codegen_expr(out, symbol_table, a) ), #(ref out => codegen_expr(out, symbol_table, b) ), #(ref out => codegen_expr(out, symbol_table, c) ) ) + ( $(ref out => codegen_expr(out, symbol_table, a) ), $(ref out => codegen_expr(out, symbol_table, b) ), $(ref out => codegen_expr(out, symbol_table, c) ) ) } } //elmi::Expr::Shader(ShaderSource, HashSet, HashSet), - _ => quote_in! { *tokens => #(format!("{:?}", expr)) }, + _ => quote_in! { *tokens => $(format!("{:?}", expr)) }, } } @@ -650,14 +650,14 @@ mod rust_codegen { match def { elmi::Def::Def(name, expr) => { quote_in! { *tokens => - let #name = #(ref out => codegen_expr(out, symbol_table, expr) ); + let $name = $(ref out => codegen_expr(out, symbol_table, expr) ); } } elmi::Def::TailDef(name, arg_names, expr) => { quote_in! { *tokens => - |#(for arg in arg_names join (, ) => mut #arg) | { - #("'")#name : loop { - #(ref out => codegen_expr(out, symbol_table, expr)) + |$(for arg in arg_names join (, ) => mut $arg) | { + $("'")$name : loop { + $(ref out => codegen_expr(out, symbol_table, expr)) } } } @@ -736,10 +736,10 @@ mod lua_codegen { body: &elmi::Expr, ) { quote_in! { *tokens => - function #name(#(for (parameter, _tipe) in parameters.iter() join (, ) => - #parameter + function $name($(for (parameter, _tipe) in parameters.iter() join (, ) => + $parameter )) - #(ref out { codegen_expr(out, symbol_table, body) }) + $(ref out { codegen_expr(out, symbol_table, body) }) end } } @@ -750,7 +750,7 @@ mod lua_codegen { name: &elmi::Name, ) { quote_in! { *tokens => - #(ref out => codegen_home_to_builder(out, home) )__#name + $(ref out => codegen_home_to_builder(out, home) )__$name } } @@ -761,7 +761,7 @@ mod lua_codegen { } = global; quote_in! { *tokens => - _#(author.replace("-", "_"))_#(project.replace("-", "_"))__#(home.0.replace(".", "_")) + _$(author.replace("-", "_"))_$(project.replace("-", "_"))__$(home.0.replace(".", "_")) } } @@ -769,18 +769,18 @@ mod lua_codegen { match expr { elmi::Expr::Bool(true) => quote_in! { *tokens => true }, elmi::Expr::Bool(false) => quote_in! { *tokens => false }, - elmi::Expr::Chr(c) => quote_in! { *tokens => #(quoted(c)) }, - elmi::Expr::Str(s) => quote_in! { *tokens => #(quoted(s)) }, - elmi::Expr::Int(x) => quote_in! { *tokens => #(x.to_string()) }, - elmi::Expr::Float(x) => quote_in! { *tokens => #(x.to_string()) }, + elmi::Expr::Chr(c) => quote_in! { *tokens => $(quoted(c)) }, + elmi::Expr::Str(s) => quote_in! { *tokens => $(quoted(s)) }, + elmi::Expr::Int(x) => quote_in! { *tokens => $(x.to_string()) }, + elmi::Expr::Float(x) => quote_in! { *tokens => $(x.to_string()) }, elmi::Expr::VarLocal(name) => { quote_in! { *tokens => - #name + $name } } elmi::Expr::VarGlobal(elmi::Global(home, name)) => { quote_in! { *tokens => - #(ref out => codegen_name_from_global(out, home, name)) + $(ref out => codegen_name_from_global(out, home, name)) } } //elmi::Expr::VarEnum(Global, IndexZeroBased), @@ -794,7 +794,7 @@ mod lua_codegen { } else { quote_in! { *tokens => &[ - #(for x in xs join (,#) => #(ref out => codegen_expr(out, symbol_table, x) ) ) + $(for x in xs join (,$) => $(ref out => codegen_expr(out, symbol_table, x) ) ) ] } } @@ -802,7 +802,7 @@ mod lua_codegen { elmi::Expr::Function(_parameters, _body) => { quote_in! { *tokens => "i don't know how to code gen a function expression" - //#(for elmi::Name(ref parameter) in parameters.iter() join (, ) => + //$(for elmi::Name(ref parameter) in parameters.iter() join (, ) => //) } } @@ -819,11 +819,11 @@ mod lua_codegen { ))); } quote_in! { *tokens => - Box::new(| #(for arg in closure_args.iter() join (, ) => #(ref out => codegen_expr(out, symbol_table, arg))) | { - #(ref out => { + Box::new(| $(for arg in closure_args.iter() join (, ) => $(ref out => codegen_expr(out, symbol_table, arg))) | { + $(ref out => { codegen_name_from_global(out, home, name) })( - #(for arg in args.iter().chain(closure_args.iter()) join (,#) => #(ref out => + $(for arg in args.iter().chain(closure_args.iter()) join (,$) => $(ref out => codegen_expr(out, symbol_table, arg) ) ) ) @@ -831,10 +831,10 @@ mod lua_codegen { } } else { quote_in! { *tokens => - #(ref out => { + $(ref out => { codegen_name_from_global(out, home, name) })( - #(for arg in args join (,#) => #(ref out => + $(for arg in args join (,$) => $(ref out => codegen_expr(out, symbol_table, arg) ) ) ) @@ -852,8 +852,8 @@ mod lua_codegen { } elmi::Expr::VarLocal(name) => { quote_in! { *tokens => - #name( - #(for arg in args join (,#) => #(ref out => + $name( + $(for arg in args join (,$) => $(ref out => codegen_expr(out, symbol_table, arg) ) ) ) @@ -864,7 +864,7 @@ mod lua_codegen { // TODO write a function that can take an expression and return the arity using // the symbol table from the bottom up. quote_in! { *tokens => - #(format!("{:?}", fexpr)) + $(format!("{:?}", fexpr)) } //panic!("calling an expression not yet supported"); } @@ -873,19 +873,19 @@ mod lua_codegen { //elmi::Expr::TailCall(Name, Vec<(Name, Expr)>), elmi::Expr::If(branches, _final_branch) => { quote_in! { *tokens => - #(for (condition, expr) in branches join (##("} else")) => - if #(ref out => codegen_expr(out, symbol_table, condition)) #("{") - #(ref out => codegen_expr(out, symbol_table, expr)) - ) #("} else {") - #(ref out => codegen_expr(out, symbol_table, expr)) - #("}") + $(for (condition, expr) in branches join ($$("} else")) => + if $(ref out => codegen_expr(out, symbol_table, condition)) $("{") + $(ref out => codegen_expr(out, symbol_table, expr)) + ) $("} else {") + $(ref out => codegen_expr(out, symbol_table, expr)) + $("}") } } elmi::Expr::Let(def, expr) => { quote_in! { *tokens => - #(ref out => codegen_def(out, symbol_table, def)) - # - #(ref out => codegen_expr(out, symbol_table, expr)) + $(ref out => codegen_def(out, symbol_table, def)) + $ + $(ref out => codegen_expr(out, symbol_table, expr)) } } //elmi::Expr::Destruct(Destructor, Box), @@ -896,7 +896,7 @@ mod lua_codegen { } elmi::Expr::Accessor(name) => { quote_in! { *tokens => - Box::new(|_v| { _v.#name }) + Box::new(|_v| { _v.$name }) } } //elmi::Expr::Access(Box, Name), @@ -905,16 +905,16 @@ mod lua_codegen { elmi::Expr::Unit => (), elmi::Expr::Tuple(a, b, None) => { quote_in! { *tokens => - ( #(ref out => codegen_expr(out, symbol_table, a) ), #(ref out => codegen_expr(out, symbol_table, b) ) ) + ( $(ref out => codegen_expr(out, symbol_table, a) ), $(ref out => codegen_expr(out, symbol_table, b) ) ) } } elmi::Expr::Tuple(a, b, Some(c)) => { quote_in! { *tokens => - ( #(ref out => codegen_expr(out, symbol_table, a) ), #(ref out => codegen_expr(out, symbol_table, b) ), #(ref out => codegen_expr(out, symbol_table, c) ) ) + ( $(ref out => codegen_expr(out, symbol_table, a) ), $(ref out => codegen_expr(out, symbol_table, b) ), $(ref out => codegen_expr(out, symbol_table, c) ) ) } } //elmi::Expr::Shader(ShaderSource, HashSet, HashSet), - _ => quote_in! { *tokens => #(format!("{:?}", expr)) }, + _ => quote_in! { *tokens => $(format!("{:?}", expr)) }, } } @@ -922,14 +922,14 @@ mod lua_codegen { match def { elmi::Def::Def(name, expr) => { quote_in! { *tokens => - local #name = #(ref out => codegen_expr(out, symbol_table, expr) ) + local $name = $(ref out => codegen_expr(out, symbol_table, expr) ) } } elmi::Def::TailDef(name, arg_names, expr) => { quote_in! { *tokens => - |#(for arg in arg_names join (, ) => mut #arg) | { - #("'")#name : loop { - #(ref out => codegen_expr(out, symbol_table, expr)) + |$(for arg in arg_names join (, ) => mut $arg) | { + $("'")$name : loop { + $(ref out => codegen_expr(out, symbol_table, expr)) } } } diff --git a/starmelon-binary.do b/starmelon-binary.do index b1f9d66..98998c7 100644 --- a/starmelon-binary.do +++ b/starmelon-binary.do @@ -1,7 +1,15 @@ -if test -f ../../target/release/starmelon; then - redo-ifchange always-rebuild - ls -al ../../../target/release/starmelon | redo-stamp -else; - cargo build --release - ls -al ../../../target/release/starmelon | redo-stamp +BINARY_PATH="../../target/release/starmelon" + +redo-ifchange Cargo.toml +find src/ -type f | xargs redo-ifchange +find ../../../infra/rust-elmi/src -type f | xargs redo-ifchange +find ../../../infra/redwood-lang/compiler/naive-wadler-prettier/src -type f | xargs redo-ifchange +find ../../../infra/rust-elm-project-utils/src -type f | xargs redo-ifchange +find ../../../infra/genco-extra/src -type f | xargs redo-ifchange +find ../../../infra/livetable/core/src -type f | xargs redo-ifchange + +if [ ! -f "$BINARY_PATH" ]; then + cargo build --release --color=always +else + ls -al "$BINARY_PATH" | redo-stamp fi