88//! not; since this is about being a 'free-er' Clojure, especially since it can't compete with it in raw
99//! power, neither speed or ecosystem, it might be worth it to leave in reader macros.
1010
11- use nom:: {
12- branch:: alt, bytes:: complete:: tag, map, sequence:: preceded, take_until, terminated, IResult ,
13- } ;
11+ use nom:: { branch:: alt, bytes:: complete:: tag, map, sequence:: preceded, take_until, terminated, IResult , Needed } ;
1412
1513use crate :: maps:: MapEntry ;
1614use crate :: persistent_list:: ToPersistentList ;
@@ -111,6 +109,12 @@ fn is_non_numeric_identifier_char(chr: char) -> bool {
111109 chr. is_alphabetic ( ) || "|?<>+-_=^%&$*!" . contains ( chr)
112110}
113111
112+ /// Returns true if given character is a minus character
113+ /// - `-`,
114+ fn is_minus_char ( chr : char ) -> bool {
115+ chr == '-'
116+ }
117+
114118/// Parses valid Clojure identifiers
115119/// Example Successes: ab, cat, -12+3, |blah|, <well>
116120/// Example Failures: 'a, 12b, ,cat
@@ -140,12 +144,25 @@ pub fn symbol_parser(input: &str) -> IResult<&str, Symbol> {
140144 identifier_parser ( input) . map ( |( rest_input, name) | ( rest_input, Symbol :: intern ( & name) ) )
141145}
142146
143- // @TODO add negatives
144147/// Parses valid integers
145148/// Example Successes: 1, 2, 4153, -12421
149+ ///
150+ ///
146151pub fn integer_parser ( input : & str ) -> IResult < & str , i32 > {
147- named ! ( integer_lexer<& str , & str >, take_while1!( |c: char | c. is_digit( 10 ) ) ) ;
148-
152+ named ! ( integer_sign<& str , & str >,
153+ map!(
154+ opt!( take_while_m_n!( 1 , 1 , is_minus_char) ) ,
155+ |maybe_minus| maybe_minus. unwrap_or( "" )
156+ )
157+ ) ;
158+ named ! ( integer_tail<& str , & str >, take_while1!( |c: char | c. is_digit( 10 ) ) ) ;
159+ named ! ( integer_lexer <& str , String >,
160+ do_parse!(
161+ sign: integer_sign >>
162+ rest_input: integer_tail >>
163+ ( format!( "{}{}" , sign, rest_input) )
164+ )
165+ ) ;
149166 integer_lexer ( input) . map ( |( rest, digits) | ( rest, digits. parse ( ) . unwrap ( ) ) )
150167}
151168// Currently used to create 'try_readers', which are readers (or
@@ -170,6 +187,7 @@ pub fn to_value_parser<I, O: ToValue>(
170187/// 1 => Value::I32(1),
171188/// 5 => Value::I32(5),
172189/// 1231415 => Value::I32(1231415)
190+ /// -2 => Value::I32(-2)
173191/// Example Failures:
174192/// 1.5, 7.1321 , 1423152621625226126431525
175193pub fn try_read_i32 ( input : & str ) -> IResult < & str , Value > {
@@ -208,7 +226,7 @@ pub fn try_read_string(input: &str) -> IResult<&str, Value> {
208226}
209227
210228// @TODO Perhaps generalize this, or even generalize it as a reader macro
211- /// Tries to parse &str into Value::PersistentListMap, or some other Value::..Map
229+ /// Tries to parse &str into Value::PersistentListMap, or some other Value::..Map
212230/// Example Successes:
213231/// {:a 1} => Value::PersistentListMap {PersistentListMap { MapEntry { :a, 1} .. ]})
214232pub fn try_read_map ( input : & str ) -> IResult < & str , Value > {
@@ -282,8 +300,8 @@ pub fn try_read(input: &str) -> IResult<&str, Value> {
282300 alt ( (
283301 try_read_map,
284302 try_read_string,
285- try_read_symbol,
286303 try_read_i32,
304+ try_read_symbol,
287305 try_read_list,
288306 try_read_vector,
289307 ) ) ,
@@ -314,3 +332,219 @@ fn consume_clojure_whitespaces(input: &str) -> IResult<&str, ()> {
314332fn is_clojure_whitespace ( c : char ) -> bool {
315333 c. is_whitespace ( ) || c == ','
316334}
335+
336+ #[ cfg( test) ]
337+ mod tests {
338+
339+ mod first_char_tests {
340+ use crate :: reader:: first_char;
341+
342+ #[ test]
343+ fn first_char_in_single_char_string ( ) {
344+ assert_eq ! ( 's' , first_char( "s" ) ) ;
345+ }
346+
347+ #[ test]
348+ fn first_char_in_multi_char_string ( ) {
349+ assert_eq ! ( 'a' , first_char( "ab" ) ) ;
350+ }
351+
352+ #[ test]
353+ #[ should_panic( expected = "called `Option::unwrap()` on a `None` value" ) ]
354+ fn first_char_in_empty_string_panics ( ) {
355+ first_char ( "" ) ;
356+ }
357+ }
358+
359+ mod cons_str_tests {
360+ use crate :: reader:: cons_str;
361+
362+ #[ test]
363+ fn concatenates_char_to_str_beginning ( ) {
364+ assert_eq ! ( "str" , cons_str( 's' , "tr" ) ) ;
365+ }
366+ }
367+
368+ mod identifier_parser_tests {
369+ use crate :: reader:: identifier_parser;
370+
371+ #[ test]
372+ fn identifier_parser_parses_valid_identifier ( ) {
373+ assert_eq ! ( Some ( ( " this" , String :: from( "input->output?" ) ) ) , identifier_parser( "input->output? this" ) . ok( ) ) ;
374+ }
375+
376+ #[ test]
377+ fn identifier_parser_does_not_parse_valid_identifier ( ) {
378+ assert_eq ! ( None , identifier_parser( "1input->output? this" ) . ok( ) ) ;
379+ }
380+
381+ #[ test]
382+ fn identifier_parser_does_not_parse_empty_input ( ) {
383+ assert_eq ! ( None , identifier_parser( "" ) . ok( ) ) ;
384+ }
385+ }
386+
387+ mod symbol_parser_tests {
388+ use crate :: reader:: symbol_parser;
389+ use crate :: symbol:: Symbol ;
390+
391+ #[ test]
392+ fn identifier_parser_parses_valid_identifier ( ) {
393+ assert_eq ! ( Some ( ( " this" , Symbol { name: String :: from( "input->output?" ) } ) ) , symbol_parser( "input->output? this" ) . ok( ) ) ;
394+ }
395+
396+ #[ test]
397+ fn identifier_parser_does_not_parse_valid_identifier ( ) {
398+ assert_eq ! ( None , symbol_parser( "1input->output? this" ) . ok( ) ) ;
399+ }
400+
401+ #[ test]
402+ fn identifier_parser_does_not_parse_empty_input ( ) {
403+ assert_eq ! ( None , symbol_parser( "" ) . ok( ) ) ;
404+ }
405+ }
406+
407+ mod integer_parser_tests {
408+ use crate :: reader:: { integer_parser, debug_try_read} ;
409+
410+ #[ test]
411+ fn integer_parser_parses_integer_one ( ) {
412+ let s = "1 " ;
413+ assert_eq ! ( Some ( ( " " , 1 ) ) , integer_parser( s) . ok( ) ) ;
414+ }
415+
416+ #[ test]
417+ fn integer_parser_parses_integer_zero ( ) {
418+ let s = "0 " ;
419+ assert_eq ! ( Some ( ( " " , 0 ) ) , integer_parser( s) . ok( ) ) ;
420+ }
421+
422+ #[ test]
423+ fn integer_parser_parses_integer_negative_one ( ) {
424+ let s = "-1 " ;
425+ assert_eq ! ( Some ( ( " " , -1 ) ) , integer_parser( s) . ok( ) ) ;
426+ }
427+
428+ #[ test]
429+ //#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }")]
430+ fn integer_parser_parses_and_fails ( ) {
431+ let s = "-1-2 " ;
432+ assert_eq ! ( Some ( ( "-2 " , -1 ) ) , integer_parser( s) . ok( ) ) ;
433+ }
434+
435+ }
436+
437+ mod try_read_symbol_tests {
438+ use crate :: value:: Value ;
439+ use crate :: symbol:: Symbol ;
440+ use crate :: reader:: try_read_symbol;
441+
442+ #[ test]
443+ fn try_read_minus_as_valid_symbol_test ( ) {
444+ assert_eq ! ( Value :: Symbol ( Symbol { name: String :: from( "-" ) } ) , try_read_symbol( "- " ) . unwrap( ) . 1 ) ;
445+ }
446+ }
447+
448+ mod try_read_tests {
449+ use crate :: reader:: try_read;
450+ use crate :: value:: { Value } ;
451+ use crate :: symbol:: Symbol ;
452+ use crate :: persistent_list_map;
453+ use crate :: persistent_list;
454+ use crate :: persistent_vector;
455+ use crate :: value:: Value :: { PersistentListMap , PersistentList , PersistentVector } ;
456+ use crate :: maps:: MapEntry ;
457+ use std:: rc:: Rc ;
458+
459+ #[ test]
460+ fn try_read_empty_map_test ( ) {
461+ assert_eq ! ( PersistentListMap ( persistent_list_map:: PersistentListMap :: Empty ) , try_read( "{} " ) . ok( ) . unwrap( ) . 1 ) ;
462+ }
463+
464+ #[ test]
465+ fn try_read_string_test ( ) {
466+ assert_eq ! ( Value :: String ( String :: from( "a string" ) ) , try_read( "\" a string\" " ) . ok( ) . unwrap( ) . 1 ) ;
467+ }
468+
469+ #[ test]
470+ fn try_read_int_test ( ) {
471+ assert_eq ! ( Value :: I32 ( 1 ) , try_read( "1 " ) . ok( ) . unwrap( ) . 1 ) ;
472+
473+ }
474+
475+ #[ test]
476+ fn try_read_negative_int_test ( ) {
477+ assert_eq ! ( Value :: I32 ( -1 ) , try_read( "-1 " ) . ok( ) . unwrap( ) . 1 ) ;
478+ }
479+
480+ #[ test]
481+ fn try_read_negative_int_with_second_dash_test ( ) {
482+ assert_eq ! ( Value :: I32 ( -1 ) , try_read( "-1-2 " ) . ok( ) . unwrap( ) . 1 ) ;
483+ }
484+
485+ #[ test]
486+ fn try_read_valid_symbol_test ( ) {
487+ assert_eq ! ( Value :: Symbol ( Symbol { name: String :: from( "my-symbol" ) } ) , try_read( "my-symbol " ) . ok( ) . unwrap( ) . 1 ) ;
488+ }
489+
490+ #[ test]
491+ fn try_read_minus_as_valid_symbol_test ( ) {
492+ assert_eq ! ( Value :: Symbol ( Symbol { name: String :: from( "-" ) } ) , try_read( "- " ) . ok( ) . unwrap( ) . 1 ) ;
493+ }
494+
495+ #[ test]
496+ fn try_read_minus_prefixed_as_valid_symbol_test ( ) {
497+ assert_eq ! ( Value :: Symbol ( Symbol { name: String :: from( "-prefixed" ) } ) , try_read( "-prefixed " ) . ok( ) . unwrap( ) . 1 ) ;
498+ }
499+
500+ #[ test]
501+ fn try_read_empty_list_test ( ) {
502+ assert_eq ! ( PersistentList ( persistent_list:: PersistentList :: Empty ) , try_read( "() " ) . ok( ) . unwrap( ) . 1 ) ;
503+ }
504+
505+ #[ test]
506+ fn try_read_empty_vector_test ( ) {
507+ assert_eq ! ( PersistentVector ( persistent_vector:: PersistentVector { vals: [ ] . to_vec( ) } ) , try_read( "[] " ) . ok( ) . unwrap( ) . 1 ) ;
508+ }
509+
510+
511+ }
512+
513+ mod consume_clojure_whitespaces_tests {
514+ use crate :: reader:: consume_clojure_whitespaces;
515+ #[ test]
516+ fn consume_whitespaces_from_input ( ) {
517+ let s = ", ,, ,1, 2, 3, 4 5,,6 " ;
518+ assert_eq ! ( Some ( ( "1, 2, 3, 4 5,,6 " , ( ) ) ) , consume_clojure_whitespaces( & s) . ok( ) ) ;
519+ }
520+ #[ test]
521+ fn consume_whitespaces_from_empty_input ( ) {
522+ let s = "" ;
523+ assert_eq ! ( None , consume_clojure_whitespaces( & s) . ok( ) ) ;
524+ }
525+ #[ test]
526+ fn consume_whitespaces_from_input_no_whitespace ( ) {
527+ let s = "1, 2, 3" ;
528+ assert_eq ! ( Some ( ( "1, 2, 3" , ( ) ) ) , consume_clojure_whitespaces( & s) . ok( ) ) ;
529+ }
530+ }
531+
532+ mod is_clojure_whitespace_tests {
533+ use crate :: reader:: is_clojure_whitespace;
534+ #[ test]
535+ fn comma_is_clojure_whitespace ( ) {
536+ assert_eq ! ( true , is_clojure_whitespace( ',' ) ) ;
537+ }
538+
539+ #[ test]
540+ fn unicode_whitespace_is_clojure_whitespace ( ) {
541+ assert_eq ! ( true , is_clojure_whitespace( ' ' ) ) ;
542+ }
543+
544+ #[ test]
545+ fn character_is_not_clojure_whitespace ( ) {
546+ assert_eq ! ( false , is_clojure_whitespace( 'a' ) ) ;
547+ }
548+ }
549+
550+ }
0 commit comments