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:: combinator:: verify;
11
+ use nom:: combinator:: { verify} ;
12
12
use nom:: {
13
13
branch:: alt, bytes:: complete:: tag, combinator:: opt, map, sequence:: preceded, take_until,
14
- terminated , AsChar , Err :: Incomplete , IResult ,
14
+ Err :: Incomplete , IResult
15
15
} ;
16
16
17
17
use crate :: keyword:: Keyword ;
18
18
use crate :: maps:: MapEntry ;
19
19
use crate :: persistent_list:: ToPersistentList ;
20
- use crate :: persistent_list_map:: ToPersistentListMap ;
20
+ use crate :: persistent_list_map:: { PersistentListMap , ToPersistentListMap , ToPersistentListMapIter } ;
21
21
use crate :: persistent_vector:: ToPersistentVector ;
22
+ use crate :: protocol:: ProtocolCastable ;
23
+ use crate :: protocol:: Protocol ;
22
24
use crate :: symbol:: Symbol ;
25
+ use crate :: error_message;
23
26
use crate :: value:: { ToValue , Value } ;
24
27
use std:: rc:: Rc ;
25
-
26
- use nom:: Err :: Error ;
27
- use std:: borrow:: Borrow ;
28
+ use crate :: protocols;
29
+ use crate :: traits:: IObj ;
28
30
use std:: io:: BufRead ;
29
31
//
30
32
// Note; the difference between ours 'parsers'
@@ -148,7 +150,7 @@ fn is_period_char(chr: char) -> bool {
148
150
///
149
151
/// Clojure defines a whitespace as either a comma or an unicode whitespace.
150
152
fn is_clojure_whitespace ( c : char ) -> bool {
151
- c. is_whitespace ( ) || c == ','
153
+ c. is_whitespace ( ) || c == ','
152
154
}
153
155
////////////////////////////////////////////////////////////////////////////////////////////////////
154
156
// End predicates
@@ -169,16 +171,16 @@ fn consume_clojure_whitespaces_parser(input: &str) -> IResult<&str, ()> {
169
171
170
172
named ! ( whitespace_parser<& str , ( ) >,
171
173
value!( ( ) ,
172
- many0!( alt!( comment_parser |
174
+ many0!( alt!( comment_parser |
173
175
take_while1!( is_clojure_whitespace) ) ) )
174
176
) ;
175
177
176
178
named ! ( no_whitespace_parser<& str , ( ) >, value!( ( ) , tag!( "" ) ) ) ;
177
179
178
- // @TODO rename / check that all parsers are consistent?
180
+ // @TODO rename / check that all parsers are consistent?
179
181
named ! ( parser<& str , ( ) >,
180
182
// 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
183
+ // its definition -- nom wouldn't allow it, as it would loop forever consuming no whitespace
182
184
// So instead, we eat up all the whitespace first, and then use the no_whitespace_parser as our sort-of
183
185
// base-case after
184
186
alt!( whitespace_parser | no_whitespace_parser)
@@ -457,6 +459,60 @@ pub fn try_read_map(input: &str) -> IResult<&str, Value> {
457
459
}
458
460
}
459
461
462
+ pub fn try_read_meta ( input : & str ) -> IResult < & str , Value > {
463
+ named ! ( meta_start<& str , & str >, preceded!( consume_clojure_whitespaces_parser, tag!( "^" ) ) ) ;
464
+ let ( rest_input, _) = meta_start ( input) ?;
465
+
466
+ let ( rest_input, meta_value) = try_read ( rest_input) ?;
467
+ let mut meta = PersistentListMap :: Empty ;
468
+ match & meta_value {
469
+ Value :: Symbol ( symbol) => {
470
+ // @TODO Note; do NOT hardcode this, make some global for TAG_KEY, like Clojure does
471
+ meta = persistent_list_map ! { "tag" => symbol} ;
472
+ } ,
473
+ Value :: Keyword ( keyword) => {
474
+ meta = persistent_list_map ! (
475
+ MapEntry {
476
+ key: meta_value. to_rc_value( ) ,
477
+ val: true . to_rc_value( )
478
+ }
479
+ ) ;
480
+ } ,
481
+ Value :: String ( string) => {
482
+ // @TODO Note; do NOT hardcode this, make some global for TAG_KEY, like Clojure does
483
+ meta = persistent_list_map ! { "tag" => string} ;
484
+ } ,
485
+ Value :: PersistentListMap ( plist_map) => {
486
+ meta = plist_map. clone ( ) ;
487
+ // Then we're already set
488
+ }
489
+ _ => {
490
+ // @TODO check instanceof IPersistentMap here instead
491
+ // @TODO Clojure has basically this one off error here, but another thing we wish to do
492
+ // is write clear errors
493
+ return Ok ( ( rest_input, error_message:: custom ( "When trying to read meta: metadata must be Symbol, Keyword, String, or Map" ) ) )
494
+ }
495
+ }
496
+ let ( rest_input, iobj_value) = try_read ( rest_input) ?;
497
+
498
+ // Extra clone, implement these functions for plain Values
499
+ if let Some ( iobj_value) = iobj_value. to_rc_value ( ) . try_as_protocol :: < protocols:: IObj > ( ) {
500
+ // @TODO get actual line and column info
501
+ let line = 1 ;
502
+ let column = 1 ;
503
+ // @TODO merge the meta iobj_value *already* has
504
+ // @TODO define some better macros and / or functions for map handling
505
+ meta = merge ! (
506
+ meta,
507
+ map_entry!( "line" , line) ,
508
+ map_entry!( "column" , column)
509
+ ) ;
510
+ Ok ( ( rest_input, iobj_value. with_meta ( meta) . unwrap ( ) . to_value ( ) ) )
511
+ }
512
+ else {
513
+ Ok ( ( rest_input, error_message:: custom ( "In meta reader: metadata can only be applied to types who are an instance of IMeta" ) ) )
514
+ }
515
+ }
460
516
// @TODO use nom functions in place of macro
461
517
/// Tries to parse &str into Value::PersistentVector
462
518
/// Example Successes:
@@ -553,12 +609,15 @@ pub fn read<R: BufRead>(reader: &mut R) -> Value {
553
609
// loop over and ask for more lines, accumulating them in input_buffer until we can read
554
610
loop {
555
611
let maybe_line = reader. by_ref ( ) . lines ( ) . next ( ) ;
556
-
612
+
557
613
match maybe_line {
558
614
Some ( Err ( e) ) => return Value :: Condition ( format ! ( "Reader error: {}" , e) ) ,
559
615
// `lines` does not include \n, but \n is part of the whitespace given to the reader
560
- // (and is important for reading comments) so we will push a newline as well
561
- Some ( Ok ( line) ) => { input_buffer. push_str ( & line) ; input_buffer. push_str ( "\n " ) ; } ,
616
+ // (and is important for reading comments) so we will push a newline as well
617
+ Some ( Ok ( line) ) => {
618
+ input_buffer. push_str ( & line) ;
619
+ input_buffer. push_str ( "\n " ) ;
620
+ }
562
621
None => {
563
622
return Value :: Condition ( String :: from ( "Tried to read empty stream; unexpected EOF" ) )
564
623
}
@@ -764,11 +823,13 @@ mod tests {
764
823
mod try_read_tests {
765
824
use crate :: persistent_list;
766
825
use crate :: persistent_list_map;
826
+ use crate :: persistent_list_map:: IPersistentMap ;
827
+ use crate :: keyword:: Keyword ;
767
828
use crate :: persistent_vector;
768
829
use crate :: reader:: try_read;
769
830
use crate :: symbol:: Symbol ;
831
+ use crate :: value:: { ToValue , Value } ;
770
832
use crate :: value:: Value :: { PersistentList , PersistentListMap , PersistentVector } ;
771
- use crate :: value:: { ToValue , Value } ;
772
833
773
834
#[ test]
774
835
fn try_read_empty_map_test ( ) {
@@ -874,6 +935,59 @@ mod tests {
874
935
fn try_read_bool_false_test ( ) {
875
936
assert_eq ! ( Value :: Boolean ( false ) , try_read( "false " ) . ok( ) . unwrap( ) . 1 )
876
937
}
938
+ #[ test]
939
+ fn try_read_meta_symbol ( ) {
940
+ let with_meta = "^cat a" ;
941
+ match try_read ( with_meta) . ok ( ) . unwrap ( ) . 1 {
942
+ Value :: Symbol ( symbol) => {
943
+ assert ! ( symbol. meta( ) . contains_key( & Keyword :: intern( "tag" ) . to_rc_value( ) ) ) ;
944
+ assert_eq ! (
945
+ Symbol :: intern( "cat" ) . to_value( ) ,
946
+ * symbol. meta( ) . get( & Keyword :: intern( "tag" ) . to_rc_value( ) )
947
+ ) ;
948
+ } ,
949
+ _ => panic ! ( "try_read_meta \" ^cat a\" should return a symbol" )
950
+ }
951
+ }
952
+ #[ test]
953
+ fn try_read_meta_string ( ) {
954
+ let with_meta = "^\" cat\" a" ;
955
+ match try_read ( with_meta) . ok ( ) . unwrap ( ) . 1 {
956
+ Value :: Symbol ( symbol) => {
957
+ assert_eq ! ( String :: from( "a" ) , symbol. name) ;
958
+ assert ! ( symbol. meta( ) . contains_key( & Keyword :: intern( "tag" ) . to_rc_value( ) ) ) ;
959
+ assert_eq ! (
960
+ "cat" . to_value( ) ,
961
+ * symbol. meta( ) . get( & Keyword :: intern( "tag" ) . to_rc_value( ) )
962
+ ) ;
963
+ } ,
964
+ _ => panic ! ( "try_read_meta '^\" cat\" a' should return a symbol" )
965
+ }
966
+ }
967
+ #[ test]
968
+ fn try_read_meta_persistent_list_map ( ) {
969
+ let with_meta = "^{:cat 1 :dog 2} a" ;
970
+ match try_read ( with_meta) . ok ( ) . unwrap ( ) . 1 {
971
+ Value :: Symbol ( symbol) => {
972
+ assert ! ( symbol. meta( ) . contains_key( & Keyword :: intern( "cat" ) . to_rc_value( ) ) ) ;
973
+ assert_eq ! ( Value :: I32 ( 1 ) , * symbol. meta( ) . get( & Keyword :: intern( "cat" ) . to_rc_value( ) ) ) ;
974
+ assert ! ( symbol. meta( ) . contains_key( & Keyword :: intern( "dog" ) . to_rc_value( ) ) ) ;
975
+ assert_eq ! ( Value :: I32 ( 2 ) , * symbol. meta( ) . get( & Keyword :: intern( "dog" ) . to_rc_value( ) ) ) ;
976
+ assert ! ( !symbol. meta( ) . contains_key( & Keyword :: intern( "chicken" ) . to_rc_value( ) ) ) ;
977
+ } ,
978
+ _ => panic ! ( "try_read_meta \" ^{:cat 1 :dog 2} a\" should return a symbol" )
979
+ }
980
+ }
981
+ #[ test]
982
+ fn try_read_meta_keyword ( ) {
983
+ let with_meta = "^:cat a" ;
984
+ match try_read ( with_meta) . ok ( ) . unwrap ( ) . 1 {
985
+ Value :: Symbol ( symbol) => {
986
+ assert ! ( symbol. meta( ) . contains_key( & Keyword :: intern( "cat" ) . to_rc_value( ) ) ) ;
987
+ } ,
988
+ _ => panic ! ( "try_read_meta \" ^:cat a\" should return a symbol" )
989
+ }
990
+ }
877
991
}
878
992
879
993
mod regex_tests {
0 commit comments