8
8
//! not; since this is about being a 'free-er' Clojure, especially since it can't compete with it in raw
9
9
//! power, neither speed or ecosystem, it might be worth it to leave in reader macros.
10
10
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 } ;
14
12
15
13
use crate :: maps:: MapEntry ;
16
14
use crate :: persistent_list:: ToPersistentList ;
@@ -111,6 +109,12 @@ fn is_non_numeric_identifier_char(chr: char) -> bool {
111
109
chr. is_alphabetic ( ) || "|?<>+-_=^%&$*!" . contains ( chr)
112
110
}
113
111
112
+ /// Returns true if given character is a minus character
113
+ /// - `-`,
114
+ fn is_minus_char ( chr : char ) -> bool {
115
+ chr == '-'
116
+ }
117
+
114
118
/// Parses valid Clojure identifiers
115
119
/// Example Successes: ab, cat, -12+3, |blah|, <well>
116
120
/// Example Failures: 'a, 12b, ,cat
@@ -140,12 +144,25 @@ pub fn symbol_parser(input: &str) -> IResult<&str, Symbol> {
140
144
identifier_parser ( input) . map ( |( rest_input, name) | ( rest_input, Symbol :: intern ( & name) ) )
141
145
}
142
146
143
- // @TODO add negatives
144
147
/// Parses valid integers
145
148
/// Example Successes: 1, 2, 4153, -12421
149
+ ///
150
+ ///
146
151
pub 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
+ ) ;
149
166
integer_lexer ( input) . map ( |( rest, digits) | ( rest, digits. parse ( ) . unwrap ( ) ) )
150
167
}
151
168
// Currently used to create 'try_readers', which are readers (or
@@ -170,6 +187,7 @@ pub fn to_value_parser<I, O: ToValue>(
170
187
/// 1 => Value::I32(1),
171
188
/// 5 => Value::I32(5),
172
189
/// 1231415 => Value::I32(1231415)
190
+ /// -2 => Value::I32(-2)
173
191
/// Example Failures:
174
192
/// 1.5, 7.1321 , 1423152621625226126431525
175
193
pub fn try_read_i32 ( input : & str ) -> IResult < & str , Value > {
@@ -208,7 +226,7 @@ pub fn try_read_string(input: &str) -> IResult<&str, Value> {
208
226
}
209
227
210
228
// @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
212
230
/// Example Successes:
213
231
/// {:a 1} => Value::PersistentListMap {PersistentListMap { MapEntry { :a, 1} .. ]})
214
232
pub fn try_read_map ( input : & str ) -> IResult < & str , Value > {
@@ -282,8 +300,8 @@ pub fn try_read(input: &str) -> IResult<&str, Value> {
282
300
alt ( (
283
301
try_read_map,
284
302
try_read_string,
285
- try_read_symbol,
286
303
try_read_i32,
304
+ try_read_symbol,
287
305
try_read_list,
288
306
try_read_vector,
289
307
) ) ,
@@ -314,3 +332,219 @@ fn consume_clojure_whitespaces(input: &str) -> IResult<&str, ()> {
314
332
fn is_clojure_whitespace ( c : char ) -> bool {
315
333
c. is_whitespace ( ) || c == ','
316
334
}
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