From 2e78b3352443951ac9fd283e72b8b967622fab27 Mon Sep 17 00:00:00 2001 From: YetAnotherMinion Date: Wed, 29 Dec 2021 14:57:35 +0000 Subject: [PATCH] feat: figured out starmelon needs expr types To generate partial application boxed closures I need to know the arity of the expression to tell if more args will remain after the given args. Right now I think I can recalculate the types if I have a symbol table where I can look up every variable. I think this will be a lot of work because I have to reimplement most of unification to deal with collections, patterns, and type variables. And I currently don't know how unification works. --- Cargo.toml | 3 +- examples/single-page/src/Main.elm | 19 ++- src/elm.rs | 7 +- src/transpile.rs | 225 ++++++++++++++++++++++++++---- 4 files changed, 223 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 613dc77..3b0a72c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ edition = "2018" # it myself. [dependencies] ahash = "0.7" -elmi = { path = "../../../infra/rust-elmi" } +elmi = { path = "../../../infra/rust-elmi", features = [ "genco" ] } naive-wadler-prettier= { path = "../../../infra/redwood-lang/compiler/naive-wadler-prettier" } os_pipe = "0.9" serde = { version = "1.0", features = [ "derive" ] } @@ -36,3 +36,4 @@ deno_web = "0.52" rusty_v8 = "0.32" futures = "0.3.15" serde_v8 = "0.15" + diff --git a/examples/single-page/src/Main.elm b/examples/single-page/src/Main.elm index c495827..bfd2446 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, badReturnType, true) +module Main exposing (view, view2, view3, view4, view5, view6, view7, view8, badReturnType, true) import Html exposing (Html, div, text) import Svg exposing (Svg, svg) @@ -50,10 +50,27 @@ view5 { x } = div [] [ text x ] +view6 : String -> Html msg +view6 model = + let + f = div [] + in + f [ text model ] + +view7 : String -> Html msg +view7 model = + (if True then div [] else div []) [ text model ] + +view8 : String -> Html msg +view8 model = + privateFunction [] [] + badReturnType : String -> Int badReturnType _ = 42 +privateFunction = div + true : Bool true = True diff --git a/src/elm.rs b/src/elm.rs index 0e4861d..9d41587 100644 --- a/src/elm.rs +++ b/src/elm.rs @@ -68,7 +68,12 @@ pub fn load_interfaces( elmi::DependencyInterface::Public(interface) => { interfaces.insert(module_name, interface); } - _ => {} + elmi::DependencyInterface::Private(package_name, unions, aliases) => { + println!("skipping private interface {}", package_name); + //for (k, v) in unions { + // println!(" {}", k); + //} + } } } } diff --git a/src/transpile.rs b/src/transpile.rs index 7531b9c..0e9d9ee 100644 --- a/src/transpile.rs +++ b/src/transpile.rs @@ -75,6 +75,67 @@ pub fn transpile( let objects = elm::load_objects(&elm_cache_dir)?; + let mut symbol_table: HashMap = HashMap::new(); + + for (key, node) in objects.iter() { + match interfaces.get(&key.0) { + None => { + //eprintln!("failed to find interface for object {}", key); + println!("skipping symbol because we don't have an interface {}", key); + //println!("{:?}", node); + } + Some(module_interface) => match module_interface.values.get(&key.1) { + None => { + println!("skipping symbol {}", key); + } + Some(annotation) => { + let elmi::CannonicalAnnotation(_free_vars, ref tipe) = annotation; + + match node { + elmi::Node::Define(elmi::Expr::Function(ref parameters, _body), _deps) => { + //println!("found a definition {} arity {}", key, parameters.len() ); + symbol_table.insert( + Symbol::Global(key), + SymbolKind::Function { + arity: parameters.len(), + tipe, + }, + ); + } + elmi::Node::Define(_, _deps) => { + let mut arity = 0; + let mut xs = tipe; + loop { + match xs { + elmi::Type::TLambda(_head, tail) => { + xs = tail; + arity += 1; + } + _ => break, + } + } + if arity == 0 { + symbol_table + .insert(Symbol::Global(key), SymbolKind::Value { tipe }); + } else { + symbol_table.insert( + Symbol::Global(key), + SymbolKind::Function { arity, tipe }, + ); + } + } + elmi::Node::DefineTailFunc(arg_names, expr, _deps) => { + println!("found tail func {}", key); + } + _ => { + symbol_table.insert(Symbol::Global(key), SymbolKind::Value { tipe }); + } + } + } + }, + } + } + if let Some(node) = objects.get(&entrypoint) { match node { elmi::Node::Define(elmi::Expr::Function(ref parameters, ref body), deps) => { @@ -83,6 +144,12 @@ pub fn transpile( } 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(); @@ -90,13 +157,14 @@ pub fn transpile( .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, @@ -169,8 +237,6 @@ pub fn transpile( } } - let _symbol_table: HashMap = HashMap::new(); - // step 6, start generating rust code using a tree visitor on each of the entry points. // Accumulate the contents of each rust module in map. // @@ -185,6 +251,21 @@ pub fn transpile( Ok(()) } +// So when I have a destructuring, I will want to take a look at the type of that symbol and +// derive the tipe of the child so I can insert it into my table +type SymbolTable<'a> = HashMap, SymbolKind<'a>>; + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +enum Symbol<'a> { + Global(&'a elmi::Global), + Local(&'a elmi::Name), +} + +enum SymbolKind<'a> { + Function { arity: usize, tipe: &'a elmi::Type }, + Value { tipe: &'a elmi::Type }, +} + fn validate_function_type(tipe: &elmi::Type) -> Result<(), TypeError> { match tipe { elmi::Type::TLambda(a, b) => { @@ -283,6 +364,7 @@ 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)], @@ -290,14 +372,14 @@ fn codegen_function( body: &elmi::Expr, ) { quote_in! { *tokens => - fn #(&name.0)#(if !type_variables.is_empty() => - <#(for elmi::Name(ref tvar) in type_variables.iter() join (, ) => + fn #name#(if !type_variables.is_empty() => + <#(for tvar in type_variables.iter() join (, ) => #tvar )> - )(#(for (elmi::Name(ref parameter), tipe) in parameters.iter() join (, ) => + )(#(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, body) }) + #(ref out { codegen_expr(out, symbol_table, body) }) } } } @@ -367,7 +449,7 @@ fn codegen_name_from_global( name: &elmi::Name, ) { quote_in! { *tokens => - #(ref out => codegen_home_to_builder(out, home) )__#(&name.0) + #(ref out => codegen_home_to_builder(out, home) )__#name } } @@ -382,7 +464,7 @@ fn codegen_home_to_builder(tokens: &mut rust::Tokens, global: &elmi::ModuleNameC } } -fn codegen_expr(tokens: &mut rust::Tokens, expr: &elmi::Expr) { +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 }, @@ -390,7 +472,7 @@ fn codegen_expr(tokens: &mut rust::Tokens, expr: &elmi::Expr) { 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(elmi::Name(ref name)) => { + elmi::Expr::VarLocal(name) => { quote_in! { *tokens => #name } @@ -411,7 +493,7 @@ fn codegen_expr(tokens: &mut rust::Tokens, expr: &elmi::Expr) { } else { quote_in! { *tokens => &[ - #(for x in xs join (,#) => #(ref out => codegen_expr(out, x) ) ) + #(for x in xs join (,#) => #(ref out => codegen_expr(out, symbol_table, x) ) ) ] } } @@ -424,39 +506,107 @@ fn codegen_expr(tokens: &mut rust::Tokens, expr: &elmi::Expr) { } } elmi::Expr::Call(ref fexpr, args) => { - quote_in! { *tokens => - #(match &**fexpr { - elmi::Expr::VarGlobal(elmi::Global(home, name)) => { - #(ref out => codegen_name_from_global(out, home, name)) + 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); + } } - _ => { - "unknown" + } + elmi::Expr::VarLocal(name) => { + quote_in! { *tokens => + #name( + #(for arg in args join (,#) => #(ref out => + codegen_expr(out, symbol_table, arg) ) + ) + ) } - })( - #(for arg in args join (,#) => #(ref out => - codegen_expr(out, 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(Vec<(Expr, Expr)>, Box), - //elmi::Expr::Let(Def, Box), + 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::Accessor(Name), + 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, a) ), #(ref out => codegen_expr(out, 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, a) ), #(ref out => codegen_expr(out, b) ), #(ref out => codegen_expr(out, 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), @@ -464,6 +614,25 @@ fn codegen_expr(tokens: &mut rust::Tokens, expr: &elmi::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)) + } + } + } + } + } +} + fn extract_function_types( mut tipe: &elmi::Type, mut nargs: usize,