Skip to content

Commit 85a364f

Browse files
committed
division symbol is now /
1 parent 54d6e40 commit 85a364f

File tree

7 files changed

+130
-11
lines changed

7 files changed

+130
-11
lines changed

src/clojure/core.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
(defmacro time [expr]
4848
(list (quote let) [(quote start) (quote (System/nanoTime)) (quote ret) expr]
4949
(quote (do
50-
(println (str "Elapsed time: " (_slash_ (- (System/nanoTime) start) 1000000.0) " msecs"))
50+
(println (str "Elapsed time: " (/ (- (System/nanoTime) start) 1000000.0) " msecs"))
5151
ret))))
5252

5353
(defn slurp [f & opts]

src/environment.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ impl Environment {
249249
let subtract_fn = rust_core::SubtractFn {};
250250
let multiply_fn = rust_core::MultiplyFn {};
251251
let divide_fn = rust_core::DivideFn {};
252+
let rem_fn = rust_core::RemFn {};
252253
let rand_fn = rust_core::RandFn {};
253254
let rand_int_fn = rust_core::RandIntFn {};
254255
let str_fn = rust_core::StrFn {};
@@ -315,7 +316,8 @@ impl Environment {
315316
environment.insert(Symbol::intern("+"), add_fn.to_rc_value());
316317
environment.insert(Symbol::intern("-"), subtract_fn.to_rc_value());
317318
environment.insert(Symbol::intern("*"), multiply_fn.to_rc_value());
318-
environment.insert(Symbol::intern("_slash_"), divide_fn.to_rc_value());
319+
environment.insert(Symbol::intern("/"), divide_fn.to_rc_value());
320+
environment.insert(Symbol::intern("rem"), rem_fn.to_rc_value());
319321
environment.insert(Symbol::intern("rand"), rand_fn.to_rc_value());
320322
environment.insert(Symbol::intern("rand-int"), rand_int_fn.to_rc_value());
321323
environment.insert(Symbol::intern("let"), let_macro.to_rc_value());

src/reader.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fn is_period_char(chr: char) -> bool {
148148
///
149149
/// Clojure defines a whitespace as either a comma or an unicode whitespace.
150150
fn is_clojure_whitespace(c: char) -> bool {
151-
c.is_whitespace() || c == ','
151+
c.is_whitespace() || c == ','
152152
}
153153
////////////////////////////////////////////////////////////////////////////////////////////////////
154154
// End predicates
@@ -169,16 +169,16 @@ fn consume_clojure_whitespaces_parser(input: &str) -> IResult<&str, ()> {
169169

170170
named!(whitespace_parser<&str,()>,
171171
value!((),
172-
many0!(alt!(comment_parser |
172+
many0!(alt!(comment_parser |
173173
take_while1!(is_clojure_whitespace))))
174174
);
175175

176176
named!(no_whitespace_parser<&str,()>, value!((),tag!("")));
177177

178-
// @TODO rename / check that all parsers are consistent?
178+
// @TODO rename / check that all parsers are consistent?
179179
named!(parser<&str,()>,
180180
// Because 'whitespace_parser' loops, we cannot include the case where there's no whitespace at all in
181-
// its definition -- nom wouldn't allow it, as it would loop forever consuming no whitespace
181+
// its definition -- nom wouldn't allow it, as it would loop forever consuming no whitespace
182182
// So instead, we eat up all the whitespace first, and then use the no_whitespace_parser as our sort-of
183183
// base-case after
184184
alt!(whitespace_parser | no_whitespace_parser)
@@ -432,6 +432,16 @@ pub fn try_read_pattern(input: &str) -> IResult<&str, Value> {
432432
Ok((rest_input, regex))
433433
}
434434

435+
/// This reader is needed for parsing the division sign /
436+
pub fn try_read_division_forward_slash(input: &str) -> IResult<&str, Value> {
437+
named!(slash_parser<&str, &str>, preceded!(consume_clojure_whitespaces_parser, tag!("/")));
438+
439+
let (rest_input, slash) = slash_parser(input)?;
440+
441+
// If an error is thrown, this will be coerced into a condition
442+
Ok((rest_input, Value::Symbol(Symbol::intern(slash))))
443+
}
444+
435445
// @TODO Perhaps generalize this, or even generalize it as a reader macro
436446
/// Tries to parse &str into Value::PersistentListMap, or some other Value::..Map
437447
/// Example Successes:
@@ -533,6 +543,7 @@ pub fn try_read(input: &str) -> IResult<&str, Value> {
533543
try_read_bool,
534544
try_read_nil,
535545
try_read_symbol,
546+
try_read_division_forward_slash,
536547
try_read_keyword,
537548
try_read_list,
538549
try_read_vector,
@@ -560,12 +571,15 @@ pub fn read<R: BufRead>(reader: &mut R) -> Value {
560571
// loop over and ask for more lines, accumulating them in input_buffer until we can read
561572
loop {
562573
let maybe_line = reader.by_ref().lines().next();
563-
574+
564575
match maybe_line {
565576
Some(Err(e)) => return Value::Condition(format!("Reader error: {}", e)),
566577
// `lines` does not include \n, but \n is part of the whitespace given to the reader
567-
// (and is important for reading comments) so we will push a newline as well
568-
Some(Ok(line)) => { input_buffer.push_str(&line); input_buffer.push_str("\n"); },
578+
// (and is important for reading comments) so we will push a newline as well
579+
Some(Ok(line)) => {
580+
input_buffer.push_str(&line);
581+
input_buffer.push_str("\n");
582+
}
569583
None => {
570584
return Value::Condition(String::from("Tried to read empty stream; unexpected EOF"))
571585
}
@@ -881,6 +895,14 @@ mod tests {
881895
fn try_read_bool_false_test() {
882896
assert_eq!(Value::Boolean(false), try_read("false ").ok().unwrap().1)
883897
}
898+
899+
#[test]
900+
fn try_read_forward_slash_test() {
901+
assert_eq!(
902+
Value::Symbol(Symbol::intern(&"/")),
903+
try_read("/ ").ok().unwrap().1
904+
);
905+
}
884906
}
885907

886908
mod regex_tests {

src/rust_core.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ pub use self::_divide_::*;
2929
pub(crate) mod _multiply_;
3030
pub use self::_multiply_::*;
3131

32+
pub(crate) mod rem;
33+
pub use self::rem::*;
34+
3235
pub(crate) mod rand;
3336
pub use self::rand::*;
3437

src/rust_core/_divide_.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ impl IFn for DivideFn {
2020
1 => {
2121
let val = args.get(0).unwrap().to_value();
2222
match val {
23+
Value::I32(0) => Value::Condition("Divide by zero".to_string()),
24+
Value::F64(0.0) => Value::Condition("Divide by zero".to_string()),
2325
Value::I32(a_) => Value::F64(1.0 / a_ as f64),
2426
Value::F64(f_) => Value::F64(1.0 / f_),
2527
_ => Value::Condition(format!(
@@ -34,6 +36,8 @@ impl IFn for DivideFn {
3436
let first_arg = args_iterator.next().unwrap();
3537
args_iterator.fold(first_arg.to_value(), |a, b| match a {
3638
Value::I32(a_) => match *b {
39+
Value::I32(0) => Value::Condition("Divide by zero".to_string()),
40+
Value::F64(0.0) => Value::Condition("Divide by zero".to_string()),
3741
Value::I32(b_) => Value::I32(a_ / b_),
3842
Value::F64(b_) => Value::F64(a_ as f64 / b_),
3943
_ => Value::Condition(format!(
@@ -43,6 +47,8 @@ impl IFn for DivideFn {
4347
)),
4448
},
4549
Value::F64(a_) => match *b {
50+
Value::I32(0) => Value::Condition("Divide by zero".to_string()),
51+
Value::F64(0.0) => Value::Condition("Divide by zero".to_string()),
4652
Value::I32(b_) => Value::F64(a_ / b_ as f64),
4753
Value::F64(b_) => Value::F64(a_ / b_),
4854
_ => Value::Condition(format!(

src/rust_core/rem.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use crate::ifn::IFn;
2+
use crate::value::{ToValue, Value};
3+
use std::rc::Rc;
4+
5+
use crate::error_message;
6+
7+
/// (rem x y)
8+
/// Calculate remainder
9+
#[derive(Debug, Clone)]
10+
pub struct RemFn {}
11+
impl ToValue for RemFn {
12+
fn to_value(&self) -> Value {
13+
Value::IFn(Rc::new(self.clone()))
14+
}
15+
}
16+
impl IFn for RemFn {
17+
fn invoke(&self, args: Vec<Rc<Value>>) -> Value {
18+
match args.len() {
19+
2 => {
20+
match args.get(0).unwrap().to_value() {
21+
Value::I32(a_) => match args.get(1).unwrap().to_value() {
22+
Value::I32(0) => Value::Condition("Divide by zero".to_string()),
23+
Value::F64(0.0) => Value::Condition("Divide by zero".to_string()),
24+
Value::I32(b_) => Value::I32(a_ % b_),
25+
Value::F64(b_) => Value::F64(a_ as f64 % b_),
26+
_b => Value::Condition(format!(
27+
// TODO: what error message should be returned regarding using typetags?
28+
"Type mismatch; Expecting: (i32 | i64 | f32 | f64), Found: {}",
29+
_b.type_tag()
30+
)),
31+
},
32+
Value::F64(a_) => match args.get(1).unwrap().to_value() {
33+
Value::I32(0) => Value::Condition("Divide by zero".to_string()),
34+
Value::F64(0.0) => Value::Condition("Divide by zero".to_string()),
35+
Value::I32(b_) => Value::F64(a_ % b_ as f64),
36+
Value::F64(b_) => Value::F64(a_ % b_),
37+
_b => Value::Condition(format!(
38+
// TODO: what error message should be returned regarding using typetags?
39+
"Type mismatch; Expecting: (i32 | i64 | f32 | f64), Found: {}",
40+
_b.type_tag()
41+
)),
42+
},
43+
_a => Value::Condition(format!(
44+
// TODO: what error message should be returned regarding using typetags?
45+
"Type mismatch: Expecting: (i32 | i64 | f32 | f64), Found: {}",
46+
_a.type_tag()
47+
)),
48+
}
49+
}
50+
_ => error_message::wrong_arg_count(2, args.len()),
51+
}
52+
}
53+
}
54+
55+
#[cfg(test)]
56+
mod tests {
57+
mod rem_tests {
58+
use crate::ifn::IFn;
59+
use crate::rust_core::rem::RemFn;
60+
use crate::value::Value;
61+
use std::rc::Rc;
62+
63+
#[test]
64+
fn rem_without_arguments_returns_error() {
65+
let rem = RemFn {};
66+
let args = vec![];
67+
assert_eq!(
68+
Value::Condition(String::from(
69+
"Wrong number of arguments given to function (Given: 0, Expected: 2)"
70+
)),
71+
rem.invoke(args)
72+
);
73+
}
74+
75+
#[test]
76+
fn rem_with_two_integer_argument_returns_remainder() {
77+
let rem = RemFn {};
78+
let args = vec![Rc::new(Value::I32(10)), Rc::new(Value::I32(3))];
79+
assert_eq!(Value::I32(1), rem.invoke(args));
80+
}
81+
}
82+
}

src/symbol.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ impl Symbol {
2828
// we need to make sure each 'character' in this case
2929
// is 1 byte, and not some other grapheme abstraction
3030
// else,these are two different indexes
31-
ns = &name[..ind];
32-
name = &name[ind + 1..];
31+
32+
// support interning of the symbol '/' for division
33+
if ind > 0 || name.len() > 1 {
34+
ns = &name[..ind];
35+
name = &name[ind + 1..];
36+
}
3337
}
3438
Symbol::intern_with_ns(ns, name)
3539
}

0 commit comments

Comments
 (0)