Skip to content

Commit 7aec297

Browse files
committed
Merge branch 'feature/clojure.string' [#41]
2 parents b5be752 + ae3d238 commit 7aec297

17 files changed

+1009
-44
lines changed

src/clojure_string.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
pub(crate) mod blank_qmark_;
2+
pub(crate) mod ends_with_qmark_;
3+
pub(crate) mod includes_qmark_;
4+
pub(crate) mod join;
5+
pub(crate) mod lower_case;
6+
pub(crate) mod reverse;
7+
pub(crate) mod starts_with_qmark_;
8+
pub(crate) mod trim;
9+
pub(crate) mod trim_newline;
10+
pub(crate) mod triml;
11+
pub(crate) mod trimr;
12+
pub(crate) mod upper_case;

src/clojure_string/blank_qmark_.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use crate::ifn::IFn;
2+
use crate::value::{ToValue, Value};
3+
use std::rc::Rc;
4+
5+
use crate::error_message;
6+
use crate::type_tag::TypeTag;
7+
8+
/// clojure.string/blank? ; returns true if nil, empty or whitespace
9+
#[derive(Debug, Clone)]
10+
pub struct BlankFn {}
11+
impl ToValue for BlankFn {
12+
fn to_value(&self) -> Value {
13+
Value::IFn(Rc::new(self.clone()))
14+
}
15+
}
16+
impl IFn for BlankFn {
17+
fn invoke(&self, args: Vec<Rc<Value>>) -> Value {
18+
if args.len() != 1 {
19+
return error_message::wrong_arg_count(1, args.len());
20+
} else {
21+
match args.get(0).unwrap().to_value() {
22+
Value::Nil => Value::Boolean(true),
23+
Value::String(s) => {
24+
if s.len() == 0 {
25+
Value::Boolean(true)
26+
} else {
27+
return Value::Boolean(
28+
s.chars()
29+
.filter(|c| !c.is_whitespace())
30+
.collect::<Vec<char>>()
31+
.len()
32+
== 0,
33+
);
34+
}
35+
}
36+
Value::String(s) => Value::String(s.chars().rev().collect()),
37+
_a => error_message::type_mismatch(TypeTag::String, &_a.to_value()),
38+
}
39+
}
40+
}
41+
}
42+
43+
#[cfg(test)]
44+
mod tests {
45+
mod reverse_tests {
46+
use crate::clojure_string::blank_qmark_::BlankFn;
47+
use crate::ifn::IFn;
48+
use crate::value::Value;
49+
use std::rc::Rc;
50+
51+
#[test]
52+
fn is_non_empty_string_blank() {
53+
let blank = BlankFn {};
54+
let s = "hello";
55+
let args = vec![Rc::new(Value::String(String::from(s)))];
56+
assert_eq!(Value::Boolean(false), blank.invoke(args));
57+
}
58+
59+
#[test]
60+
fn is_empty_string_blank() {
61+
let blank = BlankFn {};
62+
let s = "";
63+
let args = vec![Rc::new(Value::String(String::from(s)))];
64+
assert_eq!(Value::Boolean(true), blank.invoke(args));
65+
}
66+
67+
#[test]
68+
fn is_string_with_whitespace_only_blank() {
69+
let blank = BlankFn {};
70+
let s = " \t \n \r ";
71+
let args = vec![Rc::new(Value::String(String::from(s)))];
72+
assert_eq!(Value::Boolean(true), blank.invoke(args));
73+
}
74+
75+
#[test]
76+
fn is_string_with_whitespace_and_text_blank() {
77+
let blank = BlankFn {};
78+
let s = " \thello \n \r ";
79+
let args = vec![Rc::new(Value::String(String::from(s)))];
80+
assert_eq!(Value::Boolean(false), blank.invoke(args));
81+
}
82+
83+
#[test]
84+
fn is_nil_blank() {
85+
let blank = BlankFn {};
86+
let args = vec![Rc::new(Value::Nil)];
87+
assert_eq!(Value::Boolean(true), blank.invoke(args));
88+
}
89+
}
90+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use crate::ifn::IFn;
2+
use crate::value::{ToValue, Value};
3+
use std::rc::Rc;
4+
5+
use crate::error_message;
6+
use crate::type_tag::TypeTag;
7+
8+
/// clojure.string/ends-with? ; returns true if string ends with substring
9+
#[derive(Debug, Clone)]
10+
pub struct EndsWithFn {}
11+
impl ToValue for EndsWithFn {
12+
fn to_value(&self) -> Value {
13+
Value::IFn(Rc::new(self.clone()))
14+
}
15+
}
16+
impl IFn for EndsWithFn {
17+
fn invoke(&self, args: Vec<Rc<Value>>) -> Value {
18+
if args.len() != 2 {
19+
return error_message::wrong_arg_count(2, args.len());
20+
} else {
21+
match (
22+
args.get(0).unwrap().to_value(),
23+
args.get(1).unwrap().to_value(),
24+
) {
25+
(Value::String(s), Value::String(substring)) => {
26+
Value::Boolean(s.ends_with(&substring))
27+
}
28+
_a => error_message::type_mismatch(TypeTag::String, &_a.1.to_value()),
29+
}
30+
}
31+
}
32+
}
33+
34+
#[cfg(test)]
35+
mod tests {
36+
mod reverse_tests {
37+
use crate::clojure_string::ends_with_qmark_::EndsWithFn;
38+
use crate::ifn::IFn;
39+
use crate::value::Value;
40+
use std::rc::Rc;
41+
42+
#[test]
43+
fn hello_ends_with_lo() {
44+
let ends_with = EndsWithFn {};
45+
let s = "hello";
46+
let substring = "lo";
47+
let args = vec![
48+
Rc::new(Value::String(String::from(s))),
49+
Rc::new(Value::String(String::from(substring))),
50+
];
51+
assert_eq!(Value::Boolean(true), ends_with.invoke(args));
52+
}
53+
54+
#[test]
55+
fn hello_does_not_end_with_klo() {
56+
let ends_with = EndsWithFn {};
57+
let s = "hello";
58+
let substring = "klo";
59+
let args = vec![
60+
Rc::new(Value::String(String::from(s))),
61+
Rc::new(Value::String(String::from(substring))),
62+
];
63+
assert_eq!(Value::Boolean(false), ends_with.invoke(args));
64+
}
65+
66+
#[test]
67+
fn hello_ends_with_empty_string() {
68+
let ends_with = EndsWithFn {};
69+
let s = "hello";
70+
let substring = "";
71+
let args = vec![
72+
Rc::new(Value::String(String::from(s))),
73+
Rc::new(Value::String(String::from(substring))),
74+
];
75+
assert_eq!(Value::Boolean(true), ends_with.invoke(args));
76+
}
77+
}
78+
}

src/clojure_string/includes_qmark_.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use crate::ifn::IFn;
2+
use crate::value::{ToValue, Value};
3+
use std::rc::Rc;
4+
5+
use crate::error_message;
6+
use crate::type_tag::TypeTag;
7+
8+
/// clojure.string/includes? ; returns true if string contains substring
9+
#[derive(Debug, Clone)]
10+
pub struct IncludesFn {}
11+
impl ToValue for IncludesFn {
12+
fn to_value(&self) -> Value {
13+
Value::IFn(Rc::new(self.clone()))
14+
}
15+
}
16+
impl IFn for IncludesFn {
17+
fn invoke(&self, args: Vec<Rc<Value>>) -> Value {
18+
if args.len() != 2 {
19+
return error_message::wrong_arg_count(2, args.len());
20+
} else {
21+
match (
22+
args.get(0).unwrap().to_value(),
23+
args.get(1).unwrap().to_value(),
24+
) {
25+
(Value::String(s), Value::String(substring)) => {
26+
Value::Boolean(s.contains(&substring))
27+
}
28+
_a => error_message::type_mismatch(TypeTag::String, &_a.1.to_value()),
29+
}
30+
}
31+
}
32+
}
33+
34+
#[cfg(test)]
35+
mod tests {
36+
mod reverse_tests {
37+
use crate::clojure_string::includes_qmark_::IncludesFn;
38+
use crate::ifn::IFn;
39+
use crate::value::Value;
40+
use std::rc::Rc;
41+
42+
#[test]
43+
fn hello_includes_ell() {
44+
let includes = IncludesFn {};
45+
let s = "hello";
46+
let substring = "ell";
47+
let args = vec![
48+
Rc::new(Value::String(String::from(s))),
49+
Rc::new(Value::String(String::from(substring))),
50+
];
51+
assert_eq!(Value::Boolean(true), includes.invoke(args));
52+
}
53+
54+
#[test]
55+
fn hello_does_not_include_leh() {
56+
let includes = IncludesFn {};
57+
let s = "hello";
58+
let substring = "leh";
59+
let args = vec![
60+
Rc::new(Value::String(String::from(s))),
61+
Rc::new(Value::String(String::from(substring))),
62+
];
63+
assert_eq!(Value::Boolean(false), includes.invoke(args));
64+
}
65+
66+
#[test]
67+
fn hello_includes_empty_string() {
68+
let includes = IncludesFn {};
69+
let s = "hello";
70+
let substring = "";
71+
let args = vec![
72+
Rc::new(Value::String(String::from(s))),
73+
Rc::new(Value::String(String::from(substring))),
74+
];
75+
assert_eq!(Value::Boolean(true), includes.invoke(args));
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)