diff --git a/galvan-resolver/src/lib.rs b/galvan-resolver/src/lib.rs index bd45227..4a5699e 100644 --- a/galvan-resolver/src/lib.rs +++ b/galvan-resolver/src/lib.rs @@ -2,7 +2,9 @@ use std::collections::HashMap; use thiserror::Error; -use galvan_ast::{FnDecl, Ident, MainDecl, SegmentedAsts, ToplevelItem, TypeDecl, TypeIdent}; +use galvan_ast::{ + FnDecl, Ident, MainDecl, SegmentedAsts, TestDecl, ToplevelItem, TypeDecl, TypeIdent, +}; #[derive(Debug, Default)] pub struct LookupContext<'a> { @@ -19,7 +21,6 @@ pub struct LookupContext<'a> { pub main: Option<&'a ToplevelItem>, } -// TODO: derive thiserror and add proper error handling #[derive(Error)] // TODO: Include spans in errors #[derive(Debug, Error)] pub enum LookupError { diff --git a/galvan-transpiler/src/lib.rs b/galvan-transpiler/src/lib.rs index c3e58b1..571fc1f 100644 --- a/galvan-transpiler/src/lib.rs +++ b/galvan-transpiler/src/lib.rs @@ -2,8 +2,11 @@ use convert_case::{Case, Casing}; use derive_more::{Deref, Display, From}; use galvan_ast::*; use galvan_files::{FileError, Source}; +use itertools::Itertools; +use std::borrow::Cow; use std::collections::HashMap; use std::iter; +use std::ops::Deref; use thiserror::Error; pub(crate) use galvan_resolver::LookupContext; @@ -116,6 +119,8 @@ fn transpile_segmented( .join("\n\n"); let toplevel_functions = toplevel_functions.trim(); + let tests = transpile_tests(&segmented, ctx); + let modules = type_files .keys() .map(|id| sanitize_name(id)) @@ -136,7 +141,7 @@ fn transpile_segmented( "extern crate galvan; pub(crate) use ::galvan::std::*;\n pub(crate) mod {} {{\n{}\nuse crate::*;\n{}\n}}", galvan_module!(), SUPPRESS_WARNINGS, - [modules, toplevel_functions, &main].join("\n\n") + [modules, toplevel_functions, &main, &tests].join("\n\n") ) .into(), }; @@ -156,6 +161,53 @@ fn transpile_segmented( Ok(type_files.chain(iter::once(lib)).collect()) } +fn transpile_tests(segmented_asts: &SegmentedAsts, ctx: &Context) -> String { + fn test_name<'a>(desc: &Option) -> Cow<'a, str> { + desc.as_ref().map_or("test".into(), |desc| { + let snake = desc.as_str().trim_matches('\"').to_case(Case::Snake); + if snake.ends_with(|c: char| c.is_ascii_digit()) { + format!("{}_", snake).into() + } else { + snake.into() + } + }) + } + + let mut by_name: HashMap, Vec<&TestDecl>> = HashMap::new(); + for test in &segmented_asts.tests { + by_name + .entry(test_name(&test.item.name)) + .or_default() + .push(&test.item); + } + + let resolved_tests = by_name + .iter() + .flat_map(|(name, tests)| { + if tests.len() == 1 { + vec![(name.clone(), tests[0])] + } else { + tests + .iter() + .enumerate() + .map(|(i, &test)| (Cow::from(format!("{}_{}", name, i)), test)) + .collect_vec() + } + }) + .collect_vec(); + + let test_mod = "#[cfg(test)]\nmod tests {\nuse crate::*;\n".to_owned() + + resolved_tests + .iter() + .map(|t| t.transpile(ctx)) + .collect::>() + .join("\n\n") + .as_str() + + "\n}"; + + test_mod +} + fn transpile_member_functions(ty: &TypeIdent, fns: &[&FnDecl], ctx: &Context) -> String { if fns.is_empty() { return "".into(); diff --git a/galvan-transpiler/src/transpile_item/mod.rs b/galvan-transpiler/src/transpile_item/mod.rs index 9e95a18..ff1f9c8 100644 --- a/galvan-transpiler/src/transpile_item/mod.rs +++ b/galvan-transpiler/src/transpile_item/mod.rs @@ -1,4 +1,5 @@ mod assignment; +mod closure; mod fn_decl; mod function_call; mod ident; @@ -6,7 +7,7 @@ mod operator; mod statement; mod r#struct; mod task; +mod test_decl; mod toplevel; mod r#type; mod visibility; -mod closure; diff --git a/galvan-transpiler/src/transpile_item/test_decl.rs b/galvan-transpiler/src/transpile_item/test_decl.rs new file mode 100644 index 0000000..a50c17d --- /dev/null +++ b/galvan-transpiler/src/transpile_item/test_decl.rs @@ -0,0 +1,13 @@ +use crate::context::Context; +use crate::macros::transpile; +use crate::Transpile; +use galvan_ast::TestDecl; +use std::borrow::Cow; + +impl Transpile for &(Cow<'_, str>, &TestDecl) { + fn transpile(&self, ctx: &Context) -> String { + let (name, test_decl) = self; + let name = name.as_ref(); + transpile!(ctx, "#[test]\nfn {}() {{\n{}\n}}", name, test_decl.body) + } +}