Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/clojure/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
(defmacro time [expr]
(list (quote let) [(quote start) (quote (System/nanoTime)) (quote ret) expr]
(quote (do
(println (str "Elapsed time: " (_slash_ (- (System/nanoTime) start) 1000000.0) " msecs"))
(println (str "Elapsed time: " (/ (- (System/nanoTime) start) 1000000.0) " msecs"))
ret))))

(defn slurp [f & opts]
Expand Down
4 changes: 3 additions & 1 deletion src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ impl Environment {
let subtract_fn = rust_core::SubtractFn {};
let multiply_fn = rust_core::MultiplyFn {};
let divide_fn = rust_core::DivideFn {};
let rem_fn = rust_core::RemFn {};
let rand_fn = rust_core::RandFn {};
let rand_int_fn = rust_core::RandIntFn {};
let str_fn = rust_core::StrFn {};
Expand Down Expand Up @@ -315,7 +316,8 @@ impl Environment {
environment.insert(Symbol::intern("+"), add_fn.to_rc_value());
environment.insert(Symbol::intern("-"), subtract_fn.to_rc_value());
environment.insert(Symbol::intern("*"), multiply_fn.to_rc_value());
environment.insert(Symbol::intern("_slash_"), divide_fn.to_rc_value());
environment.insert(Symbol::intern("/"), divide_fn.to_rc_value());
environment.insert(Symbol::intern("rem"), rem_fn.to_rc_value());
environment.insert(Symbol::intern("rand"), rand_fn.to_rc_value());
environment.insert(Symbol::intern("rand-int"), rand_int_fn.to_rc_value());
environment.insert(Symbol::intern("let"), let_macro.to_rc_value());
Expand Down
36 changes: 29 additions & 7 deletions src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ fn is_period_char(chr: char) -> bool {
///
/// Clojure defines a whitespace as either a comma or an unicode whitespace.
fn is_clojure_whitespace(c: char) -> bool {
c.is_whitespace() || c == ','
c.is_whitespace() || c == ','
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// End predicates
Expand All @@ -169,16 +169,16 @@ fn consume_clojure_whitespaces_parser(input: &str) -> IResult<&str, ()> {

named!(whitespace_parser<&str,()>,
value!((),
many0!(alt!(comment_parser |
many0!(alt!(comment_parser |
take_while1!(is_clojure_whitespace))))
);

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

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

/// This reader is needed for parsing the division sign /
pub fn try_read_division_forward_slash(input: &str) -> IResult<&str, Value> {
named!(slash_parser<&str, &str>, preceded!(consume_clojure_whitespaces_parser, tag!("/")));

let (rest_input, slash) = slash_parser(input)?;

// If an error is thrown, this will be coerced into a condition
Ok((rest_input, Value::Symbol(Symbol::intern(slash))))
}

// @TODO Perhaps generalize this, or even generalize it as a reader macro
/// Tries to parse &str into Value::PersistentListMap, or some other Value::..Map
/// Example Successes:
Expand Down Expand Up @@ -533,6 +543,7 @@ pub fn try_read(input: &str) -> IResult<&str, Value> {
try_read_bool,
try_read_nil,
try_read_symbol,
try_read_division_forward_slash,
try_read_keyword,
try_read_list,
try_read_vector,
Expand Down Expand Up @@ -560,12 +571,15 @@ pub fn read<R: BufRead>(reader: &mut R) -> Value {
// loop over and ask for more lines, accumulating them in input_buffer until we can read
loop {
let maybe_line = reader.by_ref().lines().next();

match maybe_line {
Some(Err(e)) => return Value::Condition(format!("Reader error: {}", e)),
// `lines` does not include \n, but \n is part of the whitespace given to the reader
// (and is important for reading comments) so we will push a newline as well
Some(Ok(line)) => { input_buffer.push_str(&line); input_buffer.push_str("\n"); },
// (and is important for reading comments) so we will push a newline as well
Some(Ok(line)) => {
input_buffer.push_str(&line);
input_buffer.push_str("\n");
}
None => {
return Value::Condition(String::from("Tried to read empty stream; unexpected EOF"))
}
Expand Down Expand Up @@ -881,6 +895,14 @@ mod tests {
fn try_read_bool_false_test() {
assert_eq!(Value::Boolean(false), try_read("false ").ok().unwrap().1)
}

#[test]
fn try_read_forward_slash_test() {
assert_eq!(
Value::Symbol(Symbol::intern(&"/")),
try_read("/ ").ok().unwrap().1
);
}
}

mod regex_tests {
Expand Down
3 changes: 3 additions & 0 deletions src/rust_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ pub use self::_divide_::*;
pub(crate) mod _multiply_;
pub use self::_multiply_::*;

pub(crate) mod rem;
pub use self::rem::*;

pub(crate) mod rand;
pub use self::rand::*;

Expand Down
6 changes: 6 additions & 0 deletions src/rust_core/_divide_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ impl IFn for DivideFn {
1 => {
let val = args.get(0).unwrap().to_value();
match val {
Value::I32(0) => Value::Condition("Divide by zero".to_string()),
Value::F64(0.0) => Value::Condition("Divide by zero".to_string()),
Value::I32(a_) => Value::F64(1.0 / a_ as f64),
Value::F64(f_) => Value::F64(1.0 / f_),
_ => Value::Condition(format!(
Expand All @@ -34,6 +36,8 @@ impl IFn for DivideFn {
let first_arg = args_iterator.next().unwrap();
args_iterator.fold(first_arg.to_value(), |a, b| match a {
Value::I32(a_) => match *b {
Value::I32(0) => Value::Condition("Divide by zero".to_string()),
Value::F64(0.0) => Value::Condition("Divide by zero".to_string()),
Value::I32(b_) => Value::I32(a_ / b_),
Value::F64(b_) => Value::F64(a_ as f64 / b_),
_ => Value::Condition(format!(
Expand All @@ -43,6 +47,8 @@ impl IFn for DivideFn {
)),
},
Value::F64(a_) => match *b {
Value::I32(0) => Value::Condition("Divide by zero".to_string()),
Value::F64(0.0) => Value::Condition("Divide by zero".to_string()),
Value::I32(b_) => Value::F64(a_ / b_ as f64),
Value::F64(b_) => Value::F64(a_ / b_),
_ => Value::Condition(format!(
Expand Down
82 changes: 82 additions & 0 deletions src/rust_core/rem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::ifn::IFn;
use crate::value::{ToValue, Value};
use std::rc::Rc;

use crate::error_message;

/// (rem x y)
/// Calculate remainder
#[derive(Debug, Clone)]
pub struct RemFn {}
impl ToValue for RemFn {
fn to_value(&self) -> Value {
Value::IFn(Rc::new(self.clone()))
}
}
impl IFn for RemFn {
fn invoke(&self, args: Vec<Rc<Value>>) -> Value {
match args.len() {
2 => {
match args.get(0).unwrap().to_value() {
Value::I32(a_) => match args.get(1).unwrap().to_value() {
Value::I32(0) => Value::Condition("Divide by zero".to_string()),
Value::F64(0.0) => Value::Condition("Divide by zero".to_string()),
Value::I32(b_) => Value::I32(a_ % b_),
Value::F64(b_) => Value::F64(a_ as f64 % b_),
_b => Value::Condition(format!(
// TODO: what error message should be returned regarding using typetags?
"Type mismatch; Expecting: (i32 | i64 | f32 | f64), Found: {}",
_b.type_tag()
)),
},
Value::F64(a_) => match args.get(1).unwrap().to_value() {
Value::I32(0) => Value::Condition("Divide by zero".to_string()),
Value::F64(0.0) => Value::Condition("Divide by zero".to_string()),
Value::I32(b_) => Value::F64(a_ % b_ as f64),
Value::F64(b_) => Value::F64(a_ % b_),
_b => Value::Condition(format!(
// TODO: what error message should be returned regarding using typetags?
"Type mismatch; Expecting: (i32 | i64 | f32 | f64), Found: {}",
_b.type_tag()
)),
},
_a => Value::Condition(format!(
// TODO: what error message should be returned regarding using typetags?
"Type mismatch: Expecting: (i32 | i64 | f32 | f64), Found: {}",
_a.type_tag()
)),
}
}
_ => error_message::wrong_arg_count(2, args.len()),
}
}
}

#[cfg(test)]
mod tests {
mod rem_tests {
use crate::ifn::IFn;
use crate::rust_core::rem::RemFn;
use crate::value::Value;
use std::rc::Rc;

#[test]
fn rem_without_arguments_returns_error() {
let rem = RemFn {};
let args = vec![];
assert_eq!(
Value::Condition(String::from(
"Wrong number of arguments given to function (Given: 0, Expected: 2)"
)),
rem.invoke(args)
);
}

#[test]
fn rem_with_two_integer_argument_returns_remainder() {
let rem = RemFn {};
let args = vec![Rc::new(Value::I32(10)), Rc::new(Value::I32(3))];
assert_eq!(Value::I32(1), rem.invoke(args));
}
}
}
8 changes: 6 additions & 2 deletions src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ impl Symbol {
// we need to make sure each 'character' in this case
// is 1 byte, and not some other grapheme abstraction
// else,these are two different indexes
ns = &name[..ind];
name = &name[ind + 1..];

// support interning of the symbol '/' for division
if ind > 0 || name.len() > 1 {
ns = &name[..ind];
name = &name[ind + 1..];
}
}
Symbol::intern_with_ns(ns, name)
}
Expand Down