diff --git a/src/clojure/core.clj b/src/clojure/core.clj index 9cce094..4376483 100644 --- a/src/clojure/core.clj +++ b/src/clojure/core.clj @@ -2,7 +2,7 @@ (def *print-readably* true) (defmacro when [test & body] - (list 'if test (concat (list 'do) body))) + (list 'if test (cons 'do body))) (def list (fn [& ls] ls)) @@ -66,3 +66,23 @@ (defn ffirst [x] (first (first x))) + + +; proof of concept: cond + +; cond requires exceptions, temporary work around +; should use special form +(defmacro throw + [exception-form] + (println (str (first exception-form)) ": " (second exception-form))) + +; proof of concept: cond as expressed (almost) in Clojure +(defmacro cond + [& clauses] + (when clauses + (list 'if (first clauses) + (if (next clauses) + (second clauses) + (throw (IllegalArgumentException + "cond requires an even number of forms"))) + (cons 'clojure.core/cond (next (next clauses)))))) diff --git a/src/environment.rs b/src/environment.rs index 98b37e8..4072fef 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -307,6 +307,7 @@ impl Environment { let ns_macro = rust_core::NsMacro::new(Rc::clone(&environment)); let load_file_fn = rust_core::LoadFileFn::new(Rc::clone(&environment)); let refer_fn = rust_core::ReferFn::new(Rc::clone(&environment)); + let cons_fn = rust_core::ConsFn {}; // @TODO after we merge this with all the other commits we have, // just change all the `insert`s here to use insert_in_namespace // I prefer explicity and the non-dependence-on-environmental-factors @@ -454,6 +455,7 @@ impl Environment { environment.insert(Symbol::intern("more"), more_fn.to_rc_value()); environment.insert(Symbol::intern("first"), first_fn.to_rc_value()); environment.insert(Symbol::intern("second"), second_fn.to_rc_value()); + environment.insert(Symbol::intern("cons"), cons_fn.to_rc_value()); // input and output environment.insert( Symbol::intern("system-newline"), diff --git a/src/reader.rs b/src/reader.rs index fe76f7f..d2418e1 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -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 @@ -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) @@ -560,12 +560,15 @@ pub fn read(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")) } diff --git a/src/rust_core.rs b/src/rust_core.rs index b870bb5..4561bb6 100644 --- a/src/rust_core.rs +++ b/src/rust_core.rs @@ -50,6 +50,8 @@ pub(crate) mod get; pub use self::get::*; pub(crate) mod map; pub use self::map::*; +pub(crate) mod cons; +pub use self::cons::*; pub(crate) mod more; pub use self::more::*; diff --git a/src/rust_core/concat.rs b/src/rust_core/concat.rs index 51fb7f9..a5cc32e 100644 --- a/src/rust_core/concat.rs +++ b/src/rust_core/concat.rs @@ -33,3 +33,52 @@ impl IFn for ConcatFn { Value::PersistentList(concatted_vec.into_iter().collect::()) } } + +#[cfg(test)] +mod tests { + mod concat_tests { + use crate::ifn::IFn; + use crate::persistent_list::PersistentList; + use crate::persistent_vector::PersistentVector; + use crate::rust_core::ConcatFn; + use crate::value::Value; + use std::rc::Rc; + + #[test] + fn concat_test() { + let concat = ConcatFn {}; + let s = "insert as first"; + let args = vec![ + Rc::new(Value::PersistentVector( + vec![ + Rc::new(Value::String(String::from("1.1"))), + Rc::new(Value::String(String::from("1.2"))), + ] + .into_iter() + .collect::(), + )), + Rc::new(Value::PersistentVector( + vec![ + Rc::new(Value::String(String::from("2.1"))), + Rc::new(Value::String(String::from("2.2"))), + ] + .into_iter() + .collect::(), + )), + ]; + assert_eq!( + Value::PersistentList( + vec![ + Rc::new(Value::String(String::from("1.1"))), + Rc::new(Value::String(String::from("1.2"))), + Rc::new(Value::String(String::from("2.1"))), + Rc::new(Value::String(String::from("2.2"))) + ] + .into_iter() + .collect::() + ), + concat.invoke(args) + ); + } + } +} diff --git a/src/rust_core/cons.rs b/src/rust_core/cons.rs new file mode 100644 index 0000000..cdc89ba --- /dev/null +++ b/src/rust_core/cons.rs @@ -0,0 +1,75 @@ +use crate::ifn::IFn; +use crate::value::{ToValue, Value}; +use std::rc::Rc; + +use crate::error_message; +use crate::iterable::Iterable; +use crate::persistent_list::ToPersistentList; +use crate::protocol::ProtocolCastable; + +/// (cons x seq) +/// inserts x as first element of seq +#[derive(Debug, Clone)] +pub struct ConsFn {} +impl ToValue for ConsFn { + fn to_value(&self) -> Value { + Value::IFn(Rc::new(self.clone())) + } +} +impl IFn for ConsFn { + fn invoke(&self, args: Vec>) -> Value { + if args.len() != 2 { + return error_message::wrong_arg_count(2, args.len()); + } + + let mut coll_vec = match args.get(1).unwrap().try_as_protocol::() { + Some(iterable) => iterable.clone().iter().collect(), + _ => vec![], + }; + + coll_vec.insert(0, args.get(0).unwrap().to_owned()); + + return Value::PersistentList(coll_vec.into_list()); + } +} + +#[cfg(test)] +mod tests { + mod cons_tests { + use crate::ifn::IFn; + use crate::persistent_list::PersistentList; + use crate::persistent_vector::PersistentVector; + use crate::rust_core::ConsFn; + use crate::value::Value; + use std::rc::Rc; + + #[test] + fn cons_test() { + let cons = ConsFn {}; + let s = "insert as first"; + let args = vec![ + Rc::new(Value::String(String::from(s))), + Rc::new(Value::PersistentVector( + vec![ + Rc::new(Value::String(String::from("second"))), + Rc::new(Value::String(String::from("third"))), + ] + .into_iter() + .collect::(), + )), + ]; + assert_eq!( + Value::PersistentList( + vec![ + Rc::new(Value::String(String::from("insert as first"))), + Rc::new(Value::String(String::from("second"))), + Rc::new(Value::String(String::from("third"))) + ] + .into_iter() + .collect::() + ), + cons.invoke(args) + ); + } + } +} diff --git a/src/rust_core/refer.rs b/src/rust_core/refer.rs index 707e198..6bb66de 100644 --- a/src/rust_core/refer.rs +++ b/src/rust_core/refer.rs @@ -2,7 +2,7 @@ use crate::environment::Environment; use crate::error_message; use crate::ifn::IFn; use crate::keyword::Keyword; -use crate::persistent_vector::{ToPersistentVectorIter}; +use crate::persistent_vector::ToPersistentVectorIter; use crate::symbol::Symbol; use crate::type_tag::TypeTag; use crate::util::IsOdd;