diff --git a/Cargo.lock b/Cargo.lock index 3f3ffd29ab..706bc62802 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2222,6 +2222,7 @@ dependencies = [ "roc_solve", "roc_types", "roc_unify", + "ven_pretty", ] [[package]] diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 65b9264c04..cdb82509b8 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -146,6 +146,17 @@ impl fmt::Debug for Symbol { } } +impl fmt::Display for Symbol { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let module_id = self.module_id(); + let ident_id = self.ident_id(); + + match ident_id { + IdentId(value) => write!(f, "{:?}.{:?}", module_id, value), + } + } +} + fn fallback_debug_fmt(symbol: Symbol, f: &mut fmt::Formatter) -> fmt::Result { let module_id = symbol.module_id(); let ident_id = symbol.ident_id(); diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index e75d8afa11..5301d0bdc3 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -13,6 +13,7 @@ roc_types = { path = "../types" } roc_can = { path = "../can" } roc_unify = { path = "../unify" } roc_problem = { path = "../problem" } +ven_pretty = { path = "../../vendor/pretty" } bumpalo = { version = "3.2", features = ["collections"] } [dev-dependencies] diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 202e02aaa6..22b87c3820 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -11,6 +11,7 @@ use roc_problem::can::RuntimeError; use roc_region::all::{Located, Region}; use roc_types::subs::{Content, FlatType, Subs, Variable}; use std::collections::HashMap; +use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; #[derive(Clone, Debug, PartialEq)] pub struct PartialProc<'a> { @@ -799,7 +800,6 @@ pub enum MonoProblem { PatternProblem(crate::pattern::Error), } -#[allow(clippy::too_many_arguments)] impl<'a> Expr<'a> { pub fn new( env: &mut Env<'a, '_>, @@ -811,6 +811,156 @@ impl<'a> Expr<'a> { let result = from_can(env, can_expr, procs, &mut layout_cache); function_r(env, env.arena.alloc(result)) } + + pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, parens: bool) -> DocBuilder<'b, D, A> + where + D: DocAllocator<'b, A>, + D::Doc: Clone, + A: Clone, + { + use Expr::*; + + match self { + Int(lit) => alloc.text(format!("{}i64", lit)), + Float(lit) => alloc.text(format!("{}f64", lit)), + Bool(lit) => alloc.text(format!("{}", lit)), + Byte(lit) => alloc.text(format!("{}u8", lit)), + Str(lit) => alloc.text(format!("{:?}", lit)), + Load(symbol) if parens => alloc.text(format!("(Load {})", symbol)), + Load(symbol) => alloc.text(format!("Load {}", symbol)), + + DecAfter(symbol, expr) => expr + .to_doc(alloc, false) + .append(alloc.hardline()) + .append(alloc.text(format!("Dec {}", symbol))), + + Reset(symbol, expr) => alloc + .text(format!("Reset {}", symbol)) + .append(alloc.hardline()) + .append(expr.to_doc(alloc, false)), + + Reuse(symbol, expr) => alloc + .text(format!("Reuse {}", symbol)) + .append(alloc.hardline()) + .append(expr.to_doc(alloc, false)), + + Store(stores, expr) => { + let doc_stores = stores + .iter() + .map(|(symbol, _, expr)| { + alloc + .text(format!("Store {}: ", symbol)) + .append(expr.to_doc(alloc, false)) + }) + .collect::>(); + + alloc + .intersperse(doc_stores, alloc.hardline()) + .append(alloc.hardline()) + .append(expr.to_doc(alloc, false)) + } + + Cond { + branching_symbol, + pass, + fail, + .. + } => alloc + .text(format!("if {} then", branching_symbol)) + .append(alloc.hardline()) + .append(pass.1.to_doc(alloc, false).indent(4)) + .append(alloc.hardline()) + .append(alloc.text("else")) + .append(alloc.hardline()) + .append(fail.1.to_doc(alloc, false).indent(4)), + + Switch { + cond_symbol, + branches, + default_branch, + .. + } => { + let default_doc = alloc + .text("default:") + .append(alloc.hardline()) + .append(default_branch.1.to_doc(alloc, false).indent(4)) + .indent(4); + + let branches_docs = branches + .iter() + .map(|(tag, _, expr)| { + alloc + .text(format!("case {}:", tag)) + .append(alloc.hardline()) + .append(expr.to_doc(alloc, false).indent(4)) + .indent(4) + }) + .chain(std::iter::once(default_doc)); + // + alloc + .text(format!("switch {}:", cond_symbol)) + .append(alloc.hardline()) + .append( + alloc.intersperse(branches_docs, alloc.hardline().append(alloc.hardline())), + ) + .append(alloc.hardline()) + } + + Struct(fields) => alloc.text(format!("Struct {:?}", fields)), + + Tag { + tag_name, + arguments, + .. + } => { + let doc_tag = match tag_name { + TagName::Global(s) => alloc.text(s.as_str()), + TagName::Private(s) => alloc.text(format!("{}", s)), + }; + let doc_args = arguments.iter().map(|(expr, _)| expr.to_doc(alloc, true)); + + let it = std::iter::once(doc_tag).chain(doc_args); + + alloc.intersperse(it, alloc.space()) + } + AccessAtIndex { index, expr, .. } if parens => alloc + .text(format!("(Access @{} ", index)) + .append(expr.to_doc(alloc, false)) + .append(alloc.text(")")), + + AccessAtIndex { index, expr, .. } => alloc + .text(format!("Access @{} ", index)) + .append(expr.to_doc(alloc, false)), + + RunLowLevel(instr, arguments) => { + let doc_tag = alloc.text(format!("Lowlevel.{:?}", instr)); + let doc_args = arguments.iter().map(|(expr, _)| expr.to_doc(alloc, true)); + + let it = std::iter::once(doc_tag).chain(doc_args); + + if parens { + alloc + .text("(") + .append(alloc.intersperse(it, alloc.space())) + .append(alloc.text(")")) + } else { + alloc.intersperse(it, alloc.space()) + } + } + CallByName { name, .. } => alloc.text("*magic*"), + _ => todo!("not yet implemented: {:?}", self), + } + } + pub fn to_pretty(&self, width: usize) -> String { + let allocator = BoxAllocator; + let mut w = std::vec::Vec::new(); + self.to_doc::<_, ()>(&allocator, false) + .1 + .render(width, &mut w) + .unwrap(); + w.push(b'\n'); + String::from_utf8(w).unwrap() + } } enum IntOrFloat { diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 5a66ff1032..23abb32f8e 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -1,6 +1,9 @@ #[macro_use] extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + extern crate bumpalo; extern crate roc_mono; @@ -80,6 +83,57 @@ mod test_mono { assert_eq!(get_expected(interns), mono_expr); } + fn compiles_to_string(src: &str, expected: &str) { + let arena = Bump::new(); + let CanExprOut { + loc_expr, + var_store, + var, + constraint, + home, + mut interns, + .. + } = can_expr(src); + + let subs = Subs::new(var_store.into()); + let mut unify_problems = Vec::new(); + let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); + + // Compile and add all the Procs before adding main + let mut procs = Procs::default(); + let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); + + // assume 64-bit pointers + let pointer_size = std::mem::size_of::() as u32; + + // Populate Procs and Subs, and get the low-level Expr from the canonical Expr + let mut mono_problems = Vec::new(); + let mut mono_env = roc_mono::expr::Env { + arena: &arena, + subs: &mut subs, + problems: &mut mono_problems, + home, + ident_ids: &mut ident_ids, + pointer_size, + jump_counter: arena.alloc(0), + }; + let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); + let procs = + roc_mono::expr::specialize_all(&mut mono_env, procs, &mut LayoutCache::default()); + + assert_eq!( + procs.runtime_errors, + roc_collections::all::MutMap::default() + ); + + // Put this module's ident_ids back in the interns + interns.all_ident_ids.insert(home, ident_ids); + + let result = mono_expr.to_pretty(200); + + assert_eq!(result, expected); + } + #[test] fn int_literal() { compiles_to("5", Int(5)); @@ -748,4 +802,148 @@ mod test_mono { }, ) } + + #[test] + fn simple_to_string() { + compiles_to_string( + r#" + x = 3 + + x + "#, + indoc!( + r#" + Store Test.0: 3i64 + Load Test.0 + Dec Test.0 + "# + ), + ) + } + + #[test] + fn if_to_string() { + compiles_to_string( + r#" + if True then 1 else 2 + "#, + indoc!( + r#" + Store Test.0: true + if Test.0 then + 1i64 + else + 2i64 + "# + ), + ) + } + + #[test] + fn maybe_map_to_string() { + compiles_to_string( + r#" + maybe : [ Nothing, Just Int ] + maybe = Just 3 + + when maybe is + Just x -> Just (x + 1) + Nothing -> Nothing + "#, + indoc!( + r#" + Store Test.0: Just 0i64 3i64 + Store Test.0: Load Test.0 + Store Test.2: Lowlevel.And (Lowlevel.Eq 0i64 (Access @0 Load Test.0)) true + + if Test.2 then + Reset Test.0 + Reuse Test.0 + Just 0i64 *magic* + else + Reset Test.0 + Reuse Test.0 + Nothing 1i64 + Dec Test.0 + "# + ), + ) + } + + #[test] + fn very_maybe_map_to_string() { + compiles_to_string( + r#" + Maybe a : [ Nothing, Just a ] + + veryMaybe : Maybe (Maybe Int) + veryMaybe = Just (Just 3) + + when veryMaybe is + Just (Just _) -> Just (Just 1) + Just Nothing -> Just Nothing + Nothing -> Nothing + "#, + indoc!( + r#" + Store Test.1: Just 0i64 Just 0i64 3i64 + Store Test.1: Load Test.1 + Store Test.5: Lowlevel.And (Lowlevel.Eq 0i64 (Access @0 Load Test.1)) true + + if Test.5 then + + if Test.4 then + Just 0i64 Just 0i64 1i64 + else + Just 0i64 Nothing 1i64 + else + Reset Test.1 + Reuse Test.1 + Nothing 1i64 + Dec Test.1 + "# + ), + ) + } + + #[test] + fn these_map_to_string() { + compiles_to_string( + r#" + These a b : [ This a, That b, These a b ] + + these : These Int Int + these = These 1 2 + + when these is + This a -> This a + That b -> That b + These a b -> These b a + "#, + indoc!( + r#" + Store Test.1: These 1i64 1i64 2i64 + Store Test.1: Load Test.1 + + switch Test.1: + case 2: + Reset Test.1 + Reuse Test.1 + This 2i64 (Load Test.2) + + case 0: + Reset Test.1 + Reuse Test.1 + That 0i64 (Load Test.3) + + default: + Reset Test.1 + Reuse Test.1 + These 1i64 (Load Test.5) (Load Test.4) + + Dec Test.1 + "# + ), + ) + } }