Part 1b: Add keyword_map() parsing an edn::Value::Vector
into an edn::Value::map
.
This commit is contained in:
parent
8754cf224b
commit
dcc05c643c
1 changed files with 116 additions and 1 deletions
|
@ -16,17 +16,24 @@ use combine::{
|
||||||
ConsumedResult,
|
ConsumedResult,
|
||||||
ParseError,
|
ParseError,
|
||||||
Parser,
|
Parser,
|
||||||
|
ParseResult,
|
||||||
StreamOnce,
|
StreamOnce,
|
||||||
eof,
|
eof,
|
||||||
|
many,
|
||||||
|
many1,
|
||||||
|
parser,
|
||||||
satisfy,
|
satisfy,
|
||||||
satisfy_map,
|
satisfy_map,
|
||||||
};
|
};
|
||||||
use combine::primitives;
|
use combine::primitives; // To not shadow Error
|
||||||
use combine::primitives::{
|
use combine::primitives::{
|
||||||
|
Consumed,
|
||||||
FastResult,
|
FastResult,
|
||||||
};
|
};
|
||||||
use combine::combinator::{
|
use combine::combinator::{
|
||||||
Eof,
|
Eof,
|
||||||
|
Expected,
|
||||||
|
FnParser,
|
||||||
Skip,
|
Skip,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -266,6 +273,10 @@ pub fn map() -> Box<Parser<Input=Stream, Output=edn::ValueAndSpan>> {
|
||||||
satisfy(|v: edn::ValueAndSpan| v.inner.is_map()).boxed()
|
satisfy(|v: edn::ValueAndSpan| v.inner.is_map()).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn seq() -> Box<Parser<Input=Stream, Output=edn::ValueAndSpan>> {
|
||||||
|
satisfy(|v: edn::ValueAndSpan| v.inner.is_list() || v.inner.is_vector()).boxed()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn integer() -> Box<Parser<Input=Stream, Output=i64>> {
|
pub fn integer() -> Box<Parser<Input=Stream, Output=i64>> {
|
||||||
satisfy_map(|v: edn::ValueAndSpan| v.inner.as_integer()).boxed()
|
satisfy_map(|v: edn::ValueAndSpan| v.inner.as_integer()).boxed()
|
||||||
}
|
}
|
||||||
|
@ -280,3 +291,107 @@ pub fn value(value: edn::Value) -> Box<Parser<Input=Stream, Output=edn::ValueAnd
|
||||||
// trees together, we could avoid creating garbage.
|
// trees together, we could avoid creating garbage.
|
||||||
satisfy(move |v: edn::ValueAndSpan| value == v.inner.into()).boxed()
|
satisfy(move |v: edn::ValueAndSpan| value == v.inner.into()).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn keyword_map_(input: Stream) -> ParseResult<edn::ValueAndSpan, Stream>
|
||||||
|
{
|
||||||
|
// One run is a keyword followed by at one or more non-keywords.
|
||||||
|
let run = (satisfy(|v: edn::ValueAndSpan| v.inner.is_keyword()),
|
||||||
|
many1(satisfy(|v: edn::ValueAndSpan| !v.inner.is_keyword()))
|
||||||
|
.map(|vs: Vec<edn::ValueAndSpan>| {
|
||||||
|
// TODO: extract "spanning".
|
||||||
|
let beg = vs.first().unwrap().span.0;
|
||||||
|
let end = vs.last().unwrap().span.1;
|
||||||
|
edn::ValueAndSpan {
|
||||||
|
inner: edn::SpannedValue::Vector(vs),
|
||||||
|
span: edn::Span(beg, end),
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
let mut runs = vector().of(many::<Vec<_>, _>(run));
|
||||||
|
|
||||||
|
let (data, input) = try!(runs.parse_lazy(input).into());
|
||||||
|
|
||||||
|
let mut m: std::collections::BTreeMap<edn::ValueAndSpan, edn::ValueAndSpan> = std::collections::BTreeMap::default();
|
||||||
|
for (k, vs) in data {
|
||||||
|
if m.insert(k, vs).is_some() {
|
||||||
|
// TODO: improve this message.
|
||||||
|
return Err(Consumed::Empty(ParseError::from_errors(input.into_inner().position(), Vec::new())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let map = edn::ValueAndSpan {
|
||||||
|
inner: edn::SpannedValue::Map(m),
|
||||||
|
span: edn::Span(0, 0), // TODO: fix this.
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((map, input))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turn a vector of keywords and non-keyword values into a map. As an example, turn
|
||||||
|
/// ```edn
|
||||||
|
/// [:keyword1 value1 value2 ... :keyword2 value3 value4 ...]
|
||||||
|
/// ```
|
||||||
|
/// into
|
||||||
|
/// ```edn
|
||||||
|
/// {:keyword1 [value1 value2 ...] :keyword2 [value3 value4 ...]}
|
||||||
|
/// ```.
|
||||||
|
pub fn keyword_map() -> Expected<FnParser<Stream, fn(Stream) -> ParseResult<edn::ValueAndSpan, Stream>>>
|
||||||
|
{
|
||||||
|
// The `as` work arounds https://github.com/rust-lang/rust/issues/20178.
|
||||||
|
parser(keyword_map_ as fn(Stream) -> ParseResult<edn::ValueAndSpan, Stream>).expected("keyword map")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use combine::{eof};
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Take a string `input` and a string `expected` and ensure that `input` parses to an
|
||||||
|
/// `edn::Value` keyword map equivalent to the `edn::Value` that `expected` parses to.
|
||||||
|
macro_rules! assert_keyword_map_eq {
|
||||||
|
( $input: expr, $expected: expr ) => {{
|
||||||
|
let input = edn::parse::value($input).expect("to be able to parse input EDN");
|
||||||
|
let expected = $expected.map(|e| {
|
||||||
|
edn::parse::value(e).expect("to be able to parse expected EDN").without_spans()
|
||||||
|
});
|
||||||
|
let mut par = keyword_map().map(|x| x.without_spans()).skip(eof());
|
||||||
|
let result = par.parse(input.into_atom_stream()).map(|x| x.0);
|
||||||
|
assert_eq!(result.ok(), expected);
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keyword_map() {
|
||||||
|
assert_keyword_map_eq!(
|
||||||
|
"[:foo 1 2 3 :bar 4]",
|
||||||
|
Some("{:foo [1 2 3] :bar [4]}"));
|
||||||
|
|
||||||
|
// Trailing keywords aren't allowed.
|
||||||
|
assert_keyword_map_eq!(
|
||||||
|
"[:foo]",
|
||||||
|
None);
|
||||||
|
assert_keyword_map_eq!(
|
||||||
|
"[:foo 2 :bar]",
|
||||||
|
None);
|
||||||
|
|
||||||
|
// Duplicate keywords aren't allowed.
|
||||||
|
assert_keyword_map_eq!(
|
||||||
|
"[:foo 2 :foo 1]",
|
||||||
|
None);
|
||||||
|
|
||||||
|
// Starting with anything but a keyword isn't allowed.
|
||||||
|
assert_keyword_map_eq!(
|
||||||
|
"[2 :foo 1]",
|
||||||
|
None);
|
||||||
|
|
||||||
|
// Consecutive keywords aren't allowed.
|
||||||
|
assert_keyword_map_eq!(
|
||||||
|
"[:foo :bar 1]",
|
||||||
|
None);
|
||||||
|
|
||||||
|
// Empty lists return an empty map.
|
||||||
|
assert_keyword_map_eq!(
|
||||||
|
"[]",
|
||||||
|
Some("{}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue