From 95e21129a8e7a075a200fd04e47c96296f07208e Mon Sep 17 00:00:00 2001 From: YetAnotherMinion Date: Thu, 19 Jan 2023 17:23:38 +0000 Subject: [PATCH] refactor(starmelon): update v8 dependecy --- Cargo.toml | 1 - elm.json | 24 + examples/single-page/src/Main.elm | 4 +- src/derive/mod.rs | 187 ++++++ src/exec/fixtures/astrid_pages.rs | 2 +- src/exec/mod.rs | 11 +- src/reactor/ui/Router.elm | 36 ++ src/reporting.rs | 4 +- src/transpile.rs | 963 ++++++++++++++++++++---------- 9 files changed, 894 insertions(+), 338 deletions(-) create mode 100644 elm.json create mode 100644 src/derive/mod.rs create mode 100644 src/reactor/ui/Router.elm diff --git a/Cargo.toml b/Cargo.toml index e426baa..b6493ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,6 @@ oneshot = "0.1.3" # required for livetable derive macro livetable-core = { path = "../../../infra/livetable/core" } -cargo-workspace-hack = { version = "0.1", path = "../../../infra/cargo-workspace-hack" } [dev-dependencies] diff --git a/elm.json b/elm.json new file mode 100644 index 0000000..6c95abe --- /dev/null +++ b/elm.json @@ -0,0 +1,24 @@ +{ + "type": "application", + "source-directories": [ + "src/reactor/ui" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "elm/browser": "1.0.2", + "elm/core": "1.0.5", + "elm/html": "1.0.0", + "elm/time": "1.0.0" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/examples/single-page/src/Main.elm b/examples/single-page/src/Main.elm index bfd2446..e5cb1de 100644 --- a/examples/single-page/src/Main.elm +++ b/examples/single-page/src/Main.elm @@ -1,4 +1,4 @@ -module Main exposing (view, view2, view3, view4, view5, view6, view7, view8, badReturnType, true) +module Main exposing (view, view2, view3, view4, view5, view6, view7, view8, badReturnType, true, main) import Html exposing (Html, div, text) import Svg exposing (Svg, svg) @@ -6,6 +6,8 @@ import Array exposing (Array) import Bytes exposing (Bytes) import Bytes.Decode +main = text "main" + type alias Model = { a : Int diff --git a/src/derive/mod.rs b/src/derive/mod.rs new file mode 100644 index 0000000..6ed64d3 --- /dev/null +++ b/src/derive/mod.rs @@ -0,0 +1,187 @@ +use crate::elm; +use crate::reporting::{CompilerError, Problem, TypeError}; + +use livetable_core::{ColumnDefinition, ColumnType}; +use std::path::PathBuf; +use tracing::info_span; + +pub(crate) fn derive_livetable( + file: PathBuf, + debug: bool, + _output: Option, + verbosity: u64, + //sqlite_path: Option, +) -> 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()); + } + + // step 2.5 get the module name out of the file. + let data = std::fs::read(&file) + .map_err(|io_err| CompilerError::ReadInputFailed(io_err, file.clone()))?; + + let entrypoint = elmi::ModuleNameCanonical { + package: elmi::PackageName::new("author", "project"), + module: crate::elm::parse_module_name(&data)?, + }; + + // 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(); + + let mut targets = Vec::new(); + + match interfaces.get(&entrypoint) { + Some(interface) => { + // todo search over the interface.values for something that has the type of derive + // macro + for (_key, annotation) in interface.values.iter() { + let elmi::CannonicalAnnotation(free_vars, tipe) = annotation; + match tipe { + elmi::Type::TType(module_name, name, args) + if module_name == "author/project/LiveTable.Derive" + && name == "DeriveEditor" => + { + if args.len() != 1 { + // We probably have a version mismatch of the tool and library. But I + // don't have any way of telling right now. + panic!( + "todo I don't understand how to parse the LiveTable Derive library" + ); + } + targets.push(resolve_table_argument(&args[0])?); + if !free_vars.is_empty() { + // We can't have free vars because that means we can't derive the serialize and + // deserialize functions without concreate types. The table editor component is + // not generic. + //panic!("todo we can't have free vars in livetable derive macro"); + } + } + _ => (), + } + } + + if targets.is_empty() { + return Err(CompilerError::MissingLiveTableDeriveMacroTarget( + file.clone(), + entrypoint.clone(), + ) + .into()); + }; + //match interface.values.get(&entrypoint.1) { + //Some(annotation) => { + // let elmi::CannonicalAnnotation(_free_vars, tipe) = annotation; + + // if is_astrid_pages_route(tipe) { + // ExecutionMode::AstridPagesRoute + // } else { + // let (input_type, output_type) = scripting::resolve_function_type(tipe)?; + // ExecutionMode::Scripting(input_type, output_type) + // } + //} + //None => return Err(CompilerError::BadImport(entrypoint).into()), + } + None => return Err(CompilerError::MissingModuleTypeInformation(entrypoint).into()), + } + drop(timing_guard); + + Ok(()) +} + +fn resolve_table_argument(tipe: &elmi::Type) -> Result { + let mut columns = Vec::new(); + match tipe { + // The `fields` field of TAlias is empty if it aliasing a record. + elmi::Type::TAlias(_module_name, _type_name, _fields, ref alias) => match &**alias { + elmi::AliasType::Filled(elmi::Type::TRecord(fields, _name)) => { + //eprintln!("has field {} with type {:?}", field_name, field_type.tipe); + eprintln!("todo resolve this type"); + // It seems the source_order is always 0. Not sure if there is a bug in my + // deserializing code or if Evan stopped saving this value but kept the file + // format. + for (field_name, field_type) in fields.iter() { + // check that column is acceptable + match &*field_type.tipe { + elmi::Type::TType(module_name, name, _args) => { + if module_name == "elm/core/Basics" && name == "Bool" { + columns.push(ColumnDefinition { + name: field_name.to_string(), + tipe: ColumnType::Bool, + sync_protocol_identifier: field_name.to_string(), + }); + } else if module_name == "elm/core/Basics" && name == "Int" { + columns.push(ColumnDefinition { + name: field_name.to_string(), + tipe: ColumnType::BigInteger, + sync_protocol_identifier: field_name.to_string(), + }); + } else if module_name == "elm/core/String" && name == "String" { + columns.push(ColumnDefinition { + name: field_name.to_string(), + tipe: ColumnType::String, + sync_protocol_identifier: field_name.to_string(), + }); + } else if module_name == "author/project/LiveTable.Types" + && name == "Email" + { + columns.push(ColumnDefinition { + name: field_name.to_string(), + tipe: ColumnType::Email, + sync_protocol_identifier: field_name.to_string(), + }); + } else if module_name == "elm/bytes/Bytes" && name == "Bytes" { + } else if module_name == "author/project/LiveTable.Types" + && name == "Attachment" + { + } else { + eprintln!("don't support field type yet {:?}\n\n", field_type.tipe); + //return Err(TypeError::OutputTypeNotSupported(tipe.clone())); + } + } + _ => { + eprintln!("don't support type yet {:?}\n\n", field_type.tipe); + //return Err(TypeError::CantEvalType(tipe.clone())) + } + } + } + Ok(3) + } + elmi::AliasType::Filled(_tipe) => { + // TODO correct error type + Err(TypeError::CantEvalHoleyAlias) + } + elmi::AliasType::Holey(_) => Err(TypeError::CantEvalHoleyAlias), + }, + _ => Err(TypeError::OutputTypeNotSupported(tipe.clone())), + } +} + +//ColumnType::MaybeBool => Maybe Bool, +//ColumnType::MaybeBigInteger => Maybe Int, +//ColumnType::MaybeString => Maybe String, +//ColumnType::Email => String, +//ColumnType::MaybeEmail => Maybe String, +//ColumnType::Url => String, +//ColumnType::MaybeUrl => Maybe String, +//ColumnType::Float => Float, +//ColumnType::MaybeFloat => Maybe Float, +//ColumnType::Double => Float, +//ColumnType::MaybeDouble => Maybe Float, +//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/exec/fixtures/astrid_pages.rs b/src/exec/fixtures/astrid_pages.rs index 50865b0..2100707 100644 --- a/src/exec/fixtures/astrid_pages.rs +++ b/src/exec/fixtures/astrid_pages.rs @@ -1,6 +1,6 @@ use crate::exec::astrid_pages::OutputType; -use genco_extra::elm::Tokens; use genco::tokens::quoted; +use genco_extra::elm::Tokens; use serde::{Deserialize, Serialize}; // I manually made this derived deserialize implementation match the elm json encoder output. The diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 9a1c150..24ed7bc 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -205,26 +205,26 @@ mod runtime { let options = WorkerOptions { bootstrap: BootstrapOptions { args: vec![], - apply_source_maps: false, cpu_count: 1, debug_flag: false, enable_testing_features: false, - is_tty: false, location: None, no_color: false, + is_tty: false, runtime_version: "0.50.0".to_string(), ts_version: "2.0.0".to_string(), unstable: false, + user_agent: "starmelon".to_string(), }, extensions: extensions, unsafely_ignore_certificate_errors: None, root_cert_store: None, - user_agent: "hello_runtime".to_string(), seed: None, module_loader, create_web_worker_cb, web_worker_preload_module_cb, - js_error_create_fn: None, + format_js_error_fn: None, + source_map_getter: None, maybe_inspector_server: None, should_break_on_first_statement: false, get_error_class_fn: Some(&get_error_class_name), @@ -233,6 +233,7 @@ mod runtime { broadcast_channel: InMemoryBroadcastChannel::default(), shared_array_buffer_store: None, compiled_wasm_module_store: None, + stdio: deno_runtime::ops::io::Stdio::default(), }; let main_module = deno_core::resolve_path(path_str)?; @@ -311,7 +312,7 @@ mod runtime { //let code = if let Some((ref source, _)) = is_data_uri { // source.to_string() //} else { - let code = self.0.to_string(); + let code: Box<[u8]> = self.0.as_bytes().into(); //}; async move { //if is_data_uri.is_none() && module_specifier.to_string() != SPECIFIER { diff --git a/src/reactor/ui/Router.elm b/src/reactor/ui/Router.elm new file mode 100644 index 0000000..e000134 --- /dev/null +++ b/src/reactor/ui/Router.elm @@ -0,0 +1,36 @@ +module Router exposing (fmain, Table) + +import Html exposing (Html, text, div) +import Time exposing (Posix) +import Foo + +fmain = + text "Hello world" + +type alias Table = + { target: String + , path: String + , start : Posix + , end : Posix + , status : Status + } + +type Status + = Published + | Draft + + +-- You could write the type out for this, but it does not matter. +--deriveTableEditor = +-- DeriveTableEditor +-- { containerAttributes = +-- [ Livetable.Attributes.rename "foobar" +-- ] +-- , fieldAttributes = +-- { target = [ columnName "Page" ] +-- , path = [ columnName "Target"] +-- , start = [ columnName "Start" ] +-- , end = [ columnName "End" ] +-- , status = [ columnName "Status" ] +-- } +-- } diff --git a/src/reporting.rs b/src/reporting.rs index 91d51bc..45023c8 100644 --- a/src/reporting.rs +++ b/src/reporting.rs @@ -256,7 +256,8 @@ impl CompilerError { vcat([to_message_bar(title, ""), Doc::text(""), message]) } } - +// Here is an example of an error message +// ``` // -- MODULE NAME MISMATCH ------------------------------ src/reactor/ui/Router.elm // // It looks like this module name is out of sync: @@ -272,6 +273,7 @@ impl CompilerError { // easier to explore unfamiliar codebases! So if you want to keep the current // module name, try renaming the file instead. // +// ``` impl InterpreterError { pub fn to_doc(&self) -> Doc { diff --git a/src/transpile.rs b/src/transpile.rs index be81e3f..51315e9 100644 --- a/src/transpile.rs +++ b/src/transpile.rs @@ -137,105 +137,75 @@ pub fn transpile( } if let Some(node) = objects.get(&entrypoint) { - match node { - elmi::Node::Define(elmi::Expr::Function(ref parameters, ref body), deps) => { - for dep in deps { - println!("I depend on {}", dep); - } - let elmi::CannonicalAnnotation(elmi::FreeVars(free_variables), tipe) = signature; - - // So I want a map of symbol to is function with arity or is value. I don't need a - // scope mechansim because elm prevents variable shadowing. So the map can - // overwrite values for sibling scopes. So everytime I visit an Expr I want to - // return the arity of that expression. One problem is I need to generate the - // arguments before serializing the body of the closure - - let (parameter_types, return_type) = - extract_function_types(&tipe, parameters.len()).unwrap(); - - let xs = parameters - .into_iter() - .zip(parameter_types.into_iter()) - .collect::>(); - - // TODO add any TLambdas in the signature to the type parameters of the functions - // as where bounds - - let mut tokens = rust::Tokens::new(); - codegen_function( - &mut tokens, - &mut symbol_table, - &entrypoint.1, - &free_variables, - &xs, - return_type, - body, - ); - println!("{}", tokens.to_file_string().unwrap()); - } - _ => println!("I don't know how to transpile that node"), - } + let mut tokens = rust::Tokens::new(); + lua_codegen::codegen( + &mut tokens, + &interfaces, + &mut symbol_table, + &entrypoint, + node, + ); } - println!("total symbols {}", objects.len()); + //println!("total symbols {}", objects.len()); //let visited: HashSet = HashSet::new(); - for (_key, node) in objects.iter() { - //println!("key {}", key); - match node { - elmi::Node::Define(_expr, deps) => { - //println!("key => {:?}", expr); - for dep in deps.iter() { - if !objects.contains_key(&dep) { - println!("could not find dep {}", dep); - } - } - break; - } - elmi::Node::DefineTailFunc(_, _, deps) => { - for dep in deps.iter() { - if !objects.contains_key(&dep) { - println!("could not find dep {}", dep); - } - } - } - elmi::Node::Ctor(_, _) => {} - elmi::Node::Enum(_) => {} - elmi::Node::Box => {} - elmi::Node::Link(dep) => { - if !objects.contains_key(&dep) { - println!("could not find dep {}", dep); - } - } - elmi::Node::Cycle(_, _, _, deps) => { - for dep in deps.iter() { - if !objects.contains_key(&dep) { - println!("could not find dep {}", dep); - } - } - } - elmi::Node::Manager(_) => (), - elmi::Node::Kernel(_, deps) => { - for dep in deps.iter() { - if !objects.contains_key(&dep) { - println!("could not find dep {}", dep); - } - } - } - elmi::Node::PortIncoming(_, deps) => { - for dep in deps.iter() { - if !objects.contains_key(&dep) { - println!("could not find dep {}", dep); - } - } - } - elmi::Node::PortOutgoing(_, deps) => { - for dep in deps.iter() { - if !objects.contains_key(&dep) { - println!("could not find dep {}", dep); - } - } - } - } - } + //for (_key, node) in objects.iter() { + // //println!("key {}", key); + // match node { + // elmi::Node::Define(_expr, deps) => { + // //println!("key => {:?}", expr); + // for dep in deps.iter() { + // if !objects.contains_key(&dep) { + // println!("could not find dep {}", dep); + // } + // } + // break; + // } + // elmi::Node::DefineTailFunc(_, _, deps) => { + // for dep in deps.iter() { + // if !objects.contains_key(&dep) { + // println!("could not find dep {}", dep); + // } + // } + // } + // elmi::Node::Ctor(_, _) => {} + // elmi::Node::Enum(_) => {} + // elmi::Node::Box => {} + // elmi::Node::Link(dep) => { + // if !objects.contains_key(&dep) { + // println!("could not find dep {}", dep); + // } + // } + // elmi::Node::Cycle(_, _, _, deps) => { + // for dep in deps.iter() { + // if !objects.contains_key(&dep) { + // println!("could not find dep {}", dep); + // } + // } + // } + // elmi::Node::Manager(_) => (), + // elmi::Node::Kernel(_, deps) => { + // for dep in deps.iter() { + // if !objects.contains_key(&dep) { + // println!("could not find dep {}", dep); + // } + // } + // } + // elmi::Node::PortIncoming(_, deps) => { + // for dep in deps.iter() { + // if !objects.contains_key(&dep) { + // println!("could not find dep {}", dep); + // } + // } + // } + // elmi::Node::PortOutgoing(_, deps) => { + // for dep in deps.iter() { + // if !objects.contains_key(&dep) { + // println!("could not find dep {}", dep); + // } + // } + // } + // } + //} // step 6, start generating rust code using a tree visitor on each of the entry points. // Accumulate the contents of each rust module in map. @@ -256,12 +226,12 @@ pub fn transpile( type SymbolTable<'a> = HashMap, SymbolKind<'a>>; #[derive(Debug, Clone, Hash, PartialEq, Eq)] -enum Symbol<'a> { +pub(crate) enum Symbol<'a> { Global(&'a elmi::Global), Local(&'a elmi::Name), } -enum SymbolKind<'a> { +pub(crate) enum SymbolKind<'a> { Function { arity: usize, tipe: &'a elmi::Type }, Value { tipe: &'a elmi::Type }, } @@ -362,270 +332,605 @@ fn validate_output_type(tipe: &elmi::Type) -> Result<(), TypeError> { } } -fn codegen_function( - tokens: &mut rust::Tokens, - symbol_table: &mut SymbolTable, - name: &elmi::Name, - type_variables: &HashSet, - parameters: &[(&elmi::Name, elmi::Type)], - return_type: elmi::Type, - body: &elmi::Expr, -) { - quote_in! { *tokens => - 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) }) +mod rust_codegen { + use super::*; + + pub(crate) fn codegen( + tokens: &mut rust::Tokens, + interfaces: &HashMap, + symbol_table: &mut SymbolTable, + symbol: &elmi::Global, + node: &elmi::Node, + ) { + match node { + elmi::Node::Define(elmi::Expr::Function(ref parameters, ref body), deps) => { + for dep in deps { + println!("I depend on {}", dep); + } + let signature = match interfaces.get(&symbol.0) { + Some(interface) => match interface.values.get(&symbol.1) { + Some(annotation) => { + let elmi::CannonicalAnnotation(_free_vars, ref tipe) = annotation; + + annotation + } + None => return, + }, + None => return, + }; + let elmi::CannonicalAnnotation(elmi::FreeVars(free_variables), tipe) = signature; + + // So I want a map of symbol to is function with arity or is value. I don't need a + // scope mechansim because elm prevents variable shadowing. So the map can + // overwrite values for sibling scopes. So everytime I visit an Expr I want to + // return the arity of that expression. One problem is I need to generate the + // arguments before serializing the body of the closure + + let (parameter_types, return_type) = + extract_function_types(&tipe, parameters.len()).unwrap(); + + let xs = parameters + .into_iter() + .zip(parameter_types.into_iter()) + .collect::>(); + + // TODO add any TLambdas in the signature to the type parameters of the functions + // as where bounds + + codegen_function( + tokens, + symbol_table, + &symbol.1, + &free_variables, + &xs, + return_type, + body, + ); + println!("{}", tokens.to_file_string().unwrap()); + } + _ => println!("I don't know how to transpile that node"), } } -} -fn codegen_type(tokens: &mut rust::Tokens, tipe: &elmi::Type) { - quote_in! { *tokens => - #(match tipe { - elmi::Type::TLambda(a, b) => { - ( #(ref out => codegen_type(out, a) ) -> #(ref out => codegen_type(out, b) ) ) - } - elmi::Type::TVar(elmi::Name(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)) - } - 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)) + fn codegen_function( + tokens: &mut rust::Tokens, + symbol_table: &mut SymbolTable, + name: &elmi::Name, + type_variables: &HashSet, + parameters: &[(&elmi::Name, elmi::Type)], + return_type: elmi::Type, + body: &elmi::Expr, + ) { + quote_in! { *tokens => + fn #name#(if !type_variables.is_empty() => + <#(for tvar in type_variables.iter() join (, ) => + #tvar )> - } - // // 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) - //} - //elmi::Type::TType(_, _, _) => Err(TypeError::CantEvalCustomType), - //elmi::Type::TRecord(_, _) => Err(TypeError::CantEvalRecord), - elmi::Type::TUnit => (), - _ => { - println!("failed to solve code {:?}", tipe); - todo_tipe - }, - //elmi::Type::TTuple(_, _, _) => Err(TypeError::CantEvalTuple), - //elmi::Type::TAlias(_, _, _, ref alias) => { - // match &**alias { - // elmi::AliasType::Filled(tipe) => { - // // I think the recursion is limited to a single step. I have not tested what - // // the CannonicalAnnotation would look like for a doubly indirect alias, for - // // example for `view` below - // // ```elm - // // type alias Foo = Int - // // type alias Bar = String - // // - // // type alias Zap = Foo -> Bar - // // - // // view : Zap - // // ``` - // validate_function_type(tipe) - // } - // elmi::AliasType::Holey(_) => return Err(TypeError::CantEvalHoleyAlias), - // } - //} - }) - }; -} - -fn codegen_name_from_global( - tokens: &mut rust::Tokens, - home: &elmi::ModuleNameCanonical, - name: &elmi::Name, -) { - quote_in! { *tokens => - #(ref out => codegen_home_to_builder(out, home) )__#name - } -} - -fn codegen_home_to_builder(tokens: &mut rust::Tokens, global: &elmi::ModuleNameCanonical) { - let elmi::ModuleNameCanonical { - package: elmi::PackageName { author, project }, - module: home, - } = global; - - quote_in! { *tokens => - _#(author.replace("-", "_"))_#(project.replace("-", "_"))__#(home.0.replace(".", "_")) - } -} - -fn codegen_expr(tokens: &mut rust::Tokens, symbol_table: &mut SymbolTable, expr: &elmi::Expr) { - 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::VarLocal(name) => { - quote_in! { *tokens => - #name + )(#(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) }) } } - elmi::Expr::VarGlobal(elmi::Global(home, name)) => { - quote_in! { *tokens => - #(ref out => codegen_name_from_global(out, home, name)) - } + } + + fn codegen_type(tokens: &mut rust::Tokens, tipe: &elmi::Type) { + quote_in! { *tokens => + #(match tipe { + elmi::Type::TLambda(a, b) => { + ( #(ref out => codegen_type(out, a) ) -> #(ref out => codegen_type(out, b) ) ) + } + elmi::Type::TVar(elmi::Name(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)) + } + 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)) + )> + } + // // 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) + //} + //elmi::Type::TType(_, _, _) => Err(TypeError::CantEvalCustomType), + //elmi::Type::TRecord(_, _) => Err(TypeError::CantEvalRecord), + elmi::Type::TUnit => (), + _ => { + println!("failed to solve code {:?}", tipe); + todo_tipe + }, + //elmi::Type::TTuple(_, _, _) => Err(TypeError::CantEvalTuple), + //elmi::Type::TAlias(_, _, _, ref alias) => { + // match &**alias { + // elmi::AliasType::Filled(tipe) => { + // // I think the recursion is limited to a single step. I have not tested what + // // the CannonicalAnnotation would look like for a doubly indirect alias, for + // // example for `view` below + // // ```elm + // // type alias Foo = Int + // // type alias Bar = String + // // + // // type alias Zap = Foo -> Bar + // // + // // view : Zap + // // ``` + // validate_function_type(tipe) + // } + // elmi::AliasType::Holey(_) => return Err(TypeError::CantEvalHoleyAlias), + // } + //} + }) + }; + } + + fn codegen_name_from_global( + tokens: &mut rust::Tokens, + home: &elmi::ModuleNameCanonical, + name: &elmi::Name, + ) { + quote_in! { *tokens => + #(ref out => codegen_home_to_builder(out, home) )__#name } - //elmi::Expr::VarEnum(Global, IndexZeroBased), - //elmi::Expr::VarBox(Global), - //elmi::Expr::VarCycle(ModuleNameCanonical, Name), - //elmi::Expr::VarDebug(Name, ModuleNameCanonical, AnnotationRegion, Option), - //elmi::Expr::VarKernel(Name, Name), - elmi::Expr::List(xs) => { - if xs.is_empty() { - quote_in! { *tokens => &[] } - } else { + } + + fn codegen_home_to_builder(tokens: &mut rust::Tokens, global: &elmi::ModuleNameCanonical) { + let elmi::ModuleNameCanonical { + package: elmi::PackageName { author, project }, + module: home, + } = global; + + quote_in! { *tokens => + _#(author.replace("-", "_"))_#(project.replace("-", "_"))__#(home.0.replace(".", "_")) + } + } + + fn codegen_expr(tokens: &mut rust::Tokens, symbol_table: &mut SymbolTable, expr: &elmi::Expr) { + 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::VarLocal(name) => { quote_in! { *tokens => - &[ - #(for x in xs join (,#) => #(ref out => codegen_expr(out, symbol_table, x) ) ) - ] + #name } } - } - 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 (, ) => - //) + elmi::Expr::VarGlobal(elmi::Global(home, name)) => { + quote_in! { *tokens => + #(ref out => codegen_name_from_global(out, home, name)) + } } - } - elmi::Expr::Call(ref fexpr, args) => { - match &**fexpr { - elmi::Expr::VarGlobal(global @ elmi::Global(home, name)) => { - match symbol_table.get(&Symbol::Global(global)) { - Some(SymbolKind::Function { arity, tipe: _ }) => { - if args.len() < *arity { - let mut closure_args = Vec::new(); - for i in 0..(*arity - args.len()) { - closure_args.push(elmi::Expr::VarLocal(elmi::Name(format!( - "_partial{}", - i - )))); - } - quote_in! { *tokens => - Box::new(| #(for arg in closure_args.iter() join (, ) => #(ref out => codegen_expr(out, symbol_table, arg))) | { + //elmi::Expr::VarEnum(Global, IndexZeroBased), + //elmi::Expr::VarBox(Global), + //elmi::Expr::VarCycle(ModuleNameCanonical, Name), + //elmi::Expr::VarDebug(Name, ModuleNameCanonical, AnnotationRegion, Option), + //elmi::Expr::VarKernel(Name, Name), + elmi::Expr::List(xs) => { + if xs.is_empty() { + quote_in! { *tokens => &[] } + } else { + quote_in! { *tokens => + &[ + #(for x in xs join (,#) => #(ref out => codegen_expr(out, symbol_table, x) ) ) + ] + } + } + } + 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 (, ) => + //) + } + } + elmi::Expr::Call(ref fexpr, args) => { + match &**fexpr { + elmi::Expr::VarGlobal(global @ elmi::Global(home, name)) => { + match symbol_table.get(&Symbol::Global(global)) { + Some(SymbolKind::Function { arity, tipe: _ }) => { + if args.len() < *arity { + let mut closure_args = Vec::new(); + for i in 0..(*arity - args.len()) { + closure_args.push(elmi::Expr::VarLocal(elmi::Name( + format!("_partial{}", i), + ))); + } + quote_in! { *tokens => + 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 => + codegen_expr(out, symbol_table, arg) ) + ) + ) + }) + } + } else { + quote_in! { *tokens => #(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 join (,#) => #(ref out => codegen_expr(out, symbol_table, arg) ) ) ) - }) - } - } else { - quote_in! { *tokens => - #(ref out => { - codegen_name_from_global(out, home, name) - })( - #(for arg in args join (,#) => #(ref out => - codegen_expr(out, symbol_table, arg) ) - ) - ) + } } + //println!("found the function symbol {}, arity {}", global, arity); + } + Some(SymbolKind::Value { tipe: _ }) => { + panic!("tried to call a symbol we thought was a value: {}", global); + } + None => { + panic!("tried to call a symbol we don't know about: {}", global); } - //println!("found the function symbol {}, arity {}", global, arity); - } - Some(SymbolKind::Value { tipe: _ }) => { - panic!("tried to call a symbol we thought was a value: {}", global); - } - None => { - panic!("tried to call a symbol we don't know about: {}", global); } } - } - elmi::Expr::VarLocal(name) => { - quote_in! { *tokens => - #name( - #(for arg in args join (,#) => #(ref out => - codegen_expr(out, symbol_table, arg) ) + elmi::Expr::VarLocal(name) => { + quote_in! { *tokens => + #name( + #(for arg in args join (,#) => #(ref out => + codegen_expr(out, symbol_table, arg) ) + ) ) - ) + } } - } - _ => { - println!("I was unable to call an expression"); - // 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)) + _ => { + println!("I was unable to call an expression"); + // 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)) + } + //panic!("calling an expression not yet supported"); } - //panic!("calling an expression not yet supported"); - } - }; - } - //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)) #("{") + }; + } + //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)) - ) #("} else {") + #("}") + } + } + elmi::Expr::Let(def, expr) => { + quote_in! { *tokens => + #(ref out => codegen_def(out, symbol_table, def)) + # #(ref out => codegen_expr(out, symbol_table, expr)) - #("}") + } + } + //elmi::Expr::Destruct(Destructor, Box), + //elmi::Expr::Case(Name, Name, Decider, Vec<(i64, Expr)>) => { + elmi::Expr::Case(label, root, decider, jumps) => { + println!("jumps {:?}", jumps); + } + elmi::Expr::Accessor(name) => { + quote_in! { *tokens => + Box::new(|_v| { _v.#name }) + } + } + //elmi::Expr::Access(Box, Name), + //elmi::Expr::Update(Box, HashMap), + //elmi::Expr::Record(HashMap), + 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) ) ) + } + } + 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) ) ) + } + } + //elmi::Expr::Shader(ShaderSource, HashSet, HashSet), + _ => quote_in! { *tokens => #(format!("{:?}", expr)) }, + } + } + + fn codegen_def(tokens: &mut rust::Tokens, symbol_table: &mut SymbolTable, def: &elmi::Def) { + match def { + elmi::Def::Def(name, expr) => { + quote_in! { *tokens => + 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)) + } + } + } } } - elmi::Expr::Let(def, expr) => { - quote_in! { *tokens => - #(ref out => codegen_def(out, symbol_table, def)) - # - #(ref out => codegen_expr(out, symbol_table, expr)) - } - } - //elmi::Expr::Destruct(Destructor, Box), - //elmi::Expr::Case(Name, Name, Decider, Vec<(i64, Expr)>), - elmi::Expr::Accessor(name) => { - quote_in! { *tokens => - Box::new(|_v| { _v.#name }) - } - } - //elmi::Expr::Access(Box, Name), - //elmi::Expr::Update(Box, HashMap), - //elmi::Expr::Record(HashMap), - 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) ) ) - } - } - 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) ) ) - } - } - //elmi::Expr::Shader(ShaderSource, HashSet, HashSet), - _ => quote_in! { *tokens => #(format!("{:?}", expr)) }, } } -fn codegen_def(tokens: &mut rust::Tokens, symbol_table: &mut SymbolTable, def: &elmi::Def) { - match def { - elmi::Def::Def(name, expr) => { - quote_in! { *tokens => - let #name = #(ref out => codegen_expr(out, symbol_table, expr) ); +mod lua_codegen { + use super::*; + + pub(crate) fn codegen( + tokens: &mut rust::Tokens, + interfaces: &HashMap, + symbol_table: &mut SymbolTable, + symbol: &elmi::Global, + node: &elmi::Node, + ) { + match node { + elmi::Node::Define(elmi::Expr::Function(ref parameters, ref body), deps) => { + for dep in deps { + println!("I depend on {}", dep); + } + let signature = match interfaces.get(&symbol.0) { + Some(interface) => match interface.values.get(&symbol.1) { + Some(annotation) => { + let elmi::CannonicalAnnotation(_free_vars, ref tipe) = annotation; + + annotation + } + None => return, + }, + None => return, + }; + let elmi::CannonicalAnnotation(elmi::FreeVars(free_variables), tipe) = signature; + + // So I want a map of symbol to is function with arity or is value. I don't need a + // scope mechansim because elm prevents variable shadowing. So the map can + // overwrite values for sibling scopes. So everytime I visit an Expr I want to + // return the arity of that expression. One problem is I need to generate the + // arguments before serializing the body of the closure + + let (parameter_types, return_type) = + extract_function_types(&tipe, parameters.len()).unwrap(); + + let xs = parameters + .into_iter() + .zip(parameter_types.into_iter()) + .collect::>(); + + // TODO add any TLambdas in the signature to the type parameters of the functions + // as where bounds + + codegen_function( + tokens, + symbol_table, + &symbol.1, + &free_variables, + &xs, + return_type, + body, + ); + println!("{}", tokens.to_file_string().unwrap()); } + _ => println!("I don't know how to transpile that node"), } - elmi::Def::TailDef(name, arg_names, expr) => { - quote_in! { *tokens => - |#(for arg in arg_names join (, ) => mut #arg) | { - #("'")#name : loop { + } + + fn codegen_function( + tokens: &mut rust::Tokens, + symbol_table: &mut SymbolTable, + name: &elmi::Name, + type_variables: &HashSet, + parameters: &[(&elmi::Name, elmi::Type)], + return_type: elmi::Type, + body: &elmi::Expr, + ) { + quote_in! { *tokens => + function #name(#(for (parameter, _tipe) in parameters.iter() join (, ) => + #parameter + )) + #(ref out { codegen_expr(out, symbol_table, body) }) + end + } + } + + fn codegen_name_from_global( + tokens: &mut rust::Tokens, + home: &elmi::ModuleNameCanonical, + name: &elmi::Name, + ) { + quote_in! { *tokens => + #(ref out => codegen_home_to_builder(out, home) )__#name + } + } + + fn codegen_home_to_builder(tokens: &mut rust::Tokens, global: &elmi::ModuleNameCanonical) { + let elmi::ModuleNameCanonical { + package: elmi::PackageName { author, project }, + module: home, + } = global; + + quote_in! { *tokens => + _#(author.replace("-", "_"))_#(project.replace("-", "_"))__#(home.0.replace(".", "_")) + } + } + + fn codegen_expr(tokens: &mut rust::Tokens, symbol_table: &mut SymbolTable, expr: &elmi::Expr) { + 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::VarLocal(name) => { + quote_in! { *tokens => + #name + } + } + elmi::Expr::VarGlobal(elmi::Global(home, name)) => { + quote_in! { *tokens => + #(ref out => codegen_name_from_global(out, home, name)) + } + } + //elmi::Expr::VarEnum(Global, IndexZeroBased), + //elmi::Expr::VarBox(Global), + //elmi::Expr::VarCycle(ModuleNameCanonical, Name), + //elmi::Expr::VarDebug(Name, ModuleNameCanonical, AnnotationRegion, Option), + //elmi::Expr::VarKernel(Name, Name), + elmi::Expr::List(xs) => { + if xs.is_empty() { + quote_in! { *tokens => &[] } + } else { + quote_in! { *tokens => + &[ + #(for x in xs join (,#) => #(ref out => codegen_expr(out, symbol_table, x) ) ) + ] + } + } + } + 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 (, ) => + //) + } + } + elmi::Expr::Call(ref fexpr, args) => { + match &**fexpr { + elmi::Expr::VarGlobal(global @ elmi::Global(home, name)) => { + match symbol_table.get(&Symbol::Global(global)) { + Some(SymbolKind::Function { arity, tipe: _ }) => { + if args.len() < *arity { + let mut closure_args = Vec::new(); + for i in 0..(*arity - args.len()) { + closure_args.push(elmi::Expr::VarLocal(elmi::Name( + format!("_partial{}", i), + ))); + } + quote_in! { *tokens => + 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 => + codegen_expr(out, symbol_table, arg) ) + ) + ) + }) + } + } else { + quote_in! { *tokens => + #(ref out => { + codegen_name_from_global(out, home, name) + })( + #(for arg in args join (,#) => #(ref out => + codegen_expr(out, symbol_table, arg) ) + ) + ) + } + } + //println!("found the function symbol {}, arity {}", global, arity); + } + Some(SymbolKind::Value { tipe: _ }) => { + panic!("tried to call a symbol we thought was a value: {}", global); + } + None => { + panic!("tried to call a symbol we don't know about: {}", global); + } + } + } + elmi::Expr::VarLocal(name) => { + quote_in! { *tokens => + #name( + #(for arg in args join (,#) => #(ref out => + codegen_expr(out, symbol_table, arg) ) + ) + ) + } + } + _ => { + println!("I was unable to call an expression"); + // 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)) + } + //panic!("calling an expression not yet supported"); + } + }; + } + //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)) + #("}") + } + } + elmi::Expr::Let(def, expr) => { + quote_in! { *tokens => + #(ref out => codegen_def(out, symbol_table, def)) + # + #(ref out => codegen_expr(out, symbol_table, expr)) + } + } + //elmi::Expr::Destruct(Destructor, Box), + //elmi::Expr::Case(Name, Name, Decider, Vec<(i64, Expr)>), + elmi::Expr::Case(label, root, decider, jumps) => { + println!("decider {:#?}", decider); + println!("jumps {:?}", jumps); + } + elmi::Expr::Accessor(name) => { + quote_in! { *tokens => + Box::new(|_v| { _v.#name }) + } + } + //elmi::Expr::Access(Box, Name), + //elmi::Expr::Update(Box, HashMap), + //elmi::Expr::Record(HashMap), + 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) ) ) + } + } + 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) ) ) + } + } + //elmi::Expr::Shader(ShaderSource, HashSet, HashSet), + _ => quote_in! { *tokens => #(format!("{:?}", expr)) }, + } + } + + fn codegen_def(tokens: &mut rust::Tokens, symbol_table: &mut SymbolTable, def: &elmi::Def) { + match def { + elmi::Def::Def(name, expr) => { + quote_in! { *tokens => + 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)) + } } } }