use super::{AppendedFragment, AtomicFragment, CodeFragment, ItemDeclaration, ItemDeclarationAPI};
use itertools::Itertools;
use std::cell::RefCell;
use std::rc::Rc;
pub enum SelfReference {
None,
Immutable,
Mutable,
}
impl Default for SelfReference {
fn default() -> Self {
Self::None
}
}
pub struct FunctionArgument {
pub name: String,
pub arg_type: String,
}
impl FunctionArgument {
pub fn new(name: String, arg_type: String) -> Self {
Self { name, arg_type }
}
}
pub struct FunctionFragment {
name: String,
declaration: ItemDeclaration,
reference: SelfReference,
args: Vec<FunctionArgument>,
return_type: Option<String>,
imports: Vec<String>,
content: Rc<RefCell<AppendedFragment>>,
}
impl FunctionFragment {
pub fn new(name: String) -> Self {
Self {
name,
..Self::default()
}
}
pub fn mark_as_test(&mut self) {
self.declaration.add_attribute("test".to_owned());
}
pub fn set_return(&mut self, return_type: String) {
self.return_type = Some(return_type);
}
pub fn set_self_reference(&mut self, reference: SelfReference) {
self.reference = reference;
}
pub fn add_arg(&mut self, name: String, arg_type: String) {
self.args.push(FunctionArgument::new(name, arg_type));
}
pub fn add_import(&mut self, import: String) {
self.imports.push(import);
}
pub fn append(&mut self, fragment: Rc<RefCell<dyn CodeFragment>>) {
self.content.borrow_mut().append(fragment);
}
}
impl Default for FunctionFragment {
fn default() -> Self {
let mut declaration = ItemDeclaration::default();
let content = Rc::new(RefCell::new(AppendedFragment::new_with_separator("\n")));
declaration.set_body(content.clone());
Self {
name: String::default(),
declaration,
reference: SelfReference::default(),
args: vec![],
return_type: None,
imports: vec![],
content,
}
}
}
impl ItemDeclarationAPI for FunctionFragment {
fn mark_as_public(&mut self) {
self.declaration.mark_as_public();
}
fn is_public(&self) -> bool {
self.declaration.is_public()
}
fn add_attribute(&mut self, attribute: String) {
self.declaration.add_attribute(attribute);
}
fn document(&mut self, documentation: String) {
self.declaration.document(documentation);
}
fn set_body(&mut self, body: Rc<RefCell<dyn CodeFragment>>) {
self.declaration.set_body(body);
}
fn mark_as_declare_only(&mut self) {
self.declaration.mark_as_declare_only();
}
fn mark_for_full_implementation(&mut self) {
self.declaration.mark_for_full_implementation();
}
}
impl CodeFragment for FunctionFragment {
fn body(&self, line_width: usize) -> String {
let mut args = self
.args
.iter()
.map(|a| format!("{}: {}", a.name, a.arg_type))
.collect::<Vec<String>>();
match self.reference {
SelfReference::None => (),
SelfReference::Immutable => args.insert(0, "&self".to_owned()),
SelfReference::Mutable => args.insert(0, "&mut self".to_owned()),
};
let args_str = args.iter().format(", ").to_string();
let return_type = match &self.return_type {
Some(actual_return_type) => format!(" -> {}", actual_return_type),
None => String::default(),
};
let mut declaration = self.declaration.clone();
declaration.set_definition(Rc::new(RefCell::new(AtomicFragment::new(format!(
"fn {name}({args}){return_type}",
name = self.name,
args = args_str,
return_type = return_type
)))));
declaration.body(line_width) }
fn imports(&self) -> Vec<String> {
let mut imports = self.imports.clone();
imports.append(&mut self.content.borrow().imports());
imports
}
}
#[cfg(test)]
mod tests {
use super::super::AtomicFragment;
use super::*;
use indoc::indoc;
#[test]
fn test_empty_function() {
let f = FunctionFragment::new("foo".to_owned());
assert_eq!(f.imports(), Vec::<String>::new());
assert_eq!(f.body(80), "fn foo() {}");
}
#[test]
fn test_function_declare_only() {
let mut f = FunctionFragment::new("foo".to_owned());
f.mark_as_declare_only();
assert_eq!(f.imports(), Vec::<String>::new());
assert_eq!(f.body(80), "fn foo();");
}
#[test]
fn test_documented_function() {
let mut f = FunctionFragment::new("foo".to_owned());
f.document("This is a function.".to_owned());
assert_eq!(f.imports(), Vec::<String>::new());
assert_eq!(
f.body(80),
indoc! {"
/// This is a function.
fn foo() {}"}
);
}
#[test]
fn test_public_function() {
let mut f = FunctionFragment::new("foo".to_owned());
f.mark_as_public();
assert_eq!(f.imports(), Vec::<String>::new());
assert_eq!(f.body(80), "pub fn foo() {}");
}
#[test]
fn test_test_function() {
let mut f = FunctionFragment::new("foo".to_owned());
f.mark_as_test();
assert_eq!(f.imports(), Vec::<String>::new());
assert_eq!(
f.body(80),
indoc! {"
#[test]
fn foo() {}"}
);
}
#[test]
fn test_function_return() {
let mut f = FunctionFragment::new("foo".to_owned());
f.mark_as_public();
f.set_return("()".to_owned());
assert_eq!(f.imports(), Vec::<String>::new());
assert_eq!(f.body(80), "pub fn foo() -> () {}");
}
#[test]
fn test_function_statements_return() {
let mut f = FunctionFragment::new("foo".to_owned());
f.set_return("i64".to_owned());
f.append(Rc::new(RefCell::new(AtomicFragment {
imports: Vec::<String>::default(),
atom: "4".to_owned(),
})));
assert_eq!(f.imports(), Vec::<String>::new());
assert_eq!(
f.body(80),
indoc! {"
fn foo() -> i64 {
4
}"} );
}
#[test]
fn test_function_args() {
let mut f = FunctionFragment::new("foo".to_owned());
f.set_return("i64".to_owned());
f.add_arg("x".to_owned(), "i64".to_owned());
f.add_arg("y".to_owned(), "u64".to_owned());
f.append(Rc::new(RefCell::new(AtomicFragment {
imports: Vec::<String>::default(),
atom: "x + y".to_owned(),
})));
assert_eq!(f.imports(), Vec::<String>::new());
assert_eq!(
f.body(80),
indoc! {"
fn foo(x: i64, y: u64) -> i64 {
x + y
}"}
);
}
#[test]
fn test_function_self() {
let mut f = FunctionFragment::new("check".to_owned());
f.set_self_reference(SelfReference::Immutable);
f.append(Rc::new(RefCell::new(AtomicFragment::new(
"assert(self.value > 0);".to_owned(),
))));
assert_eq!(f.imports(), Vec::<String>::new());
assert_eq!(
f.body(80),
indoc! {"
fn check(&self) {
assert(self.value > 0);
}"}
);
}
#[test]
fn test_function_self_with_other_args() {
let mut f = FunctionFragment::new("replace".to_owned());
f.set_self_reference(SelfReference::Mutable);
f.add_arg("new_value".to_owned(), "i64".to_owned());
f.append(Rc::new(RefCell::new(AtomicFragment::new(
"self.value = new_value;".to_owned(),
))));
assert_eq!(f.imports(), Vec::<String>::new());
assert_eq!(
f.body(80),
indoc! {"
fn replace(&mut self, new_value: i64) {
self.value = new_value;
}"}
);
}
#[test]
fn test_function_imports() {
let mut f = FunctionFragment::new("foo".to_owned());
f.document("This function adds two custom numbers together.".to_owned());
f.mark_as_public();
f.add_import("crate::MyNum".to_owned());
f.set_return("MyNum".to_owned());
f.add_arg("x".to_owned(), "MyNum".to_owned());
f.add_arg("y".to_owned(), "MyNum".to_owned());
f.append(Rc::new(RefCell::new(AtomicFragment {
imports: vec!["crate::operators::plus".to_owned()],
atom: "x + y".to_owned(),
})));
assert_eq!(f.imports(), vec!["crate::MyNum", "crate::operators::plus"]);
assert_eq!(
f.body(80),
indoc! {"
/// This function adds two custom numbers together.
pub fn foo(x: MyNum, y: MyNum) -> MyNum {
x + y
}"}
);
}
}