Skip to content

Commit e04e693

Browse files
committed
add meta reader
1 parent b94a492 commit e04e693

File tree

1 file changed

+128
-14
lines changed

1 file changed

+128
-14
lines changed

src/reader.rs

Lines changed: 128 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,25 @@
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::combinator::verify;
11+
use nom::combinator::{verify};
1212
use nom::{
1313
branch::alt, bytes::complete::tag, combinator::opt, map, sequence::preceded, take_until,
14-
terminated, AsChar, Err::Incomplete, IResult,
14+
Err::Incomplete, IResult
1515
};
1616

1717
use crate::keyword::Keyword;
1818
use crate::maps::MapEntry;
1919
use crate::persistent_list::ToPersistentList;
20-
use crate::persistent_list_map::ToPersistentListMap;
20+
use crate::persistent_list_map::{PersistentListMap,ToPersistentListMap, ToPersistentListMapIter};
2121
use crate::persistent_vector::ToPersistentVector;
22+
use crate::protocol::ProtocolCastable;
23+
use crate::protocol::Protocol;
2224
use crate::symbol::Symbol;
25+
use crate::error_message;
2326
use crate::value::{ToValue, Value};
2427
use std::rc::Rc;
25-
26-
use nom::Err::Error;
27-
use std::borrow::Borrow;
28+
use crate::protocols;
29+
use crate::traits::IObj;
2830
use std::io::BufRead;
2931
//
3032
// Note; the difference between ours 'parsers'
@@ -148,7 +150,7 @@ fn is_period_char(chr: char) -> bool {
148150
///
149151
/// Clojure defines a whitespace as either a comma or an unicode whitespace.
150152
fn is_clojure_whitespace(c: char) -> bool {
151-
c.is_whitespace() || c == ','
153+
c.is_whitespace() || c == ','
152154
}
153155
////////////////////////////////////////////////////////////////////////////////////////////////////
154156
// End predicates
@@ -169,16 +171,16 @@ fn consume_clojure_whitespaces_parser(input: &str) -> IResult<&str, ()> {
169171

170172
named!(whitespace_parser<&str,()>,
171173
value!((),
172-
many0!(alt!(comment_parser |
174+
many0!(alt!(comment_parser |
173175
take_while1!(is_clojure_whitespace))))
174176
);
175177

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

178-
// @TODO rename / check that all parsers are consistent?
180+
// @TODO rename / check that all parsers are consistent?
179181
named!(parser<&str,()>,
180182
// 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
182184
// So instead, we eat up all the whitespace first, and then use the no_whitespace_parser as our sort-of
183185
// base-case after
184186
alt!(whitespace_parser | no_whitespace_parser)
@@ -457,6 +459,60 @@ pub fn try_read_map(input: &str) -> IResult<&str, Value> {
457459
}
458460
}
459461

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+
}
460516
// @TODO use nom functions in place of macro
461517
/// Tries to parse &str into Value::PersistentVector
462518
/// Example Successes:
@@ -553,12 +609,15 @@ pub fn read<R: BufRead>(reader: &mut R) -> Value {
553609
// loop over and ask for more lines, accumulating them in input_buffer until we can read
554610
loop {
555611
let maybe_line = reader.by_ref().lines().next();
556-
612+
557613
match maybe_line {
558614
Some(Err(e)) => return Value::Condition(format!("Reader error: {}", e)),
559615
// `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+
}
562621
None => {
563622
return Value::Condition(String::from("Tried to read empty stream; unexpected EOF"))
564623
}
@@ -764,11 +823,13 @@ mod tests {
764823
mod try_read_tests {
765824
use crate::persistent_list;
766825
use crate::persistent_list_map;
826+
use crate::persistent_list_map::IPersistentMap;
827+
use crate::keyword::Keyword;
767828
use crate::persistent_vector;
768829
use crate::reader::try_read;
769830
use crate::symbol::Symbol;
831+
use crate::value::{ToValue,Value};
770832
use crate::value::Value::{PersistentList, PersistentListMap, PersistentVector};
771-
use crate::value::{ToValue, Value};
772833

773834
#[test]
774835
fn try_read_empty_map_test() {
@@ -874,6 +935,59 @@ mod tests {
874935
fn try_read_bool_false_test() {
875936
assert_eq!(Value::Boolean(false), try_read("false ").ok().unwrap().1)
876937
}
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+
}
877991
}
878992

879993
mod regex_tests {

0 commit comments

Comments
 (0)