diff --git a/edn/src/namespaceable_name.rs b/edn/src/namespaceable_name.rs index 2f5ce8a6..a0aa1d99 100644 --- a/edn/src/namespaceable_name.rs +++ b/edn/src/namespaceable_name.rs @@ -89,7 +89,7 @@ impl NamespaceableName { } } - fn dwim(namespace: Option, name: T) -> Self where N: AsRef, T: AsRef { + fn new(namespace: Option, name: T) -> Self where N: AsRef, T: AsRef { if let Some(ns) = namespace { Self::namespaced(ns, name) } else { @@ -115,9 +115,9 @@ impl NamespaceableName { let name = self.name(); if name.starts_with('_') { - Self::dwim(self.namespace(), &name[1..]) + Self::new(self.namespace(), &name[1..]) } else { - Self::dwim(self.namespace(), &format!("_{}", name)) + Self::new(self.namespace(), &format!("_{}", name)) } } diff --git a/edn/src/types.rs b/edn/src/types.rs index ada7929b..27b46e4c 100644 --- a/edn/src/types.rs +++ b/edn/src/types.rs @@ -346,6 +346,13 @@ macro_rules! def_common_value_methods { def_as_ref!(as_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol); pub fn as_keyword(&self) -> Option<&symbols::Keyword> { + match self { + &$t::Keyword(ref k) => Some(k), + _ => None, + } + } + + pub fn as_plain_keyword(&self) -> Option<&symbols::Keyword> { match self { &$t::Keyword(ref k) if !k.is_namespaced() => Some(k), _ => None, @@ -376,6 +383,13 @@ macro_rules! def_common_value_methods { def_into!(into_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol,); pub fn into_keyword(self) -> Option { + match self { + $t::Keyword(k) => Some(k), + _ => None, + } + } + + pub fn into_plain_keyword(self) -> Option { match self { $t::Keyword(k) => { if !k.is_namespaced() { @@ -726,4 +740,28 @@ mod test { assert_eq!(Value::Set(BTreeSet::new()).cmp(&Value::Set(BTreeSet::new())), Ordering::Equal); assert_eq!(Value::Map(BTreeMap::new()).cmp(&Value::Map(BTreeMap::new())), Ordering::Equal); } + + #[test] + fn test_keyword_as() { + let namespaced = symbols::Keyword::namespaced("foo", "bar"); + let plain = symbols::Keyword::plain("bar"); + let n_v = Value::Keyword(namespaced); + let p_v = Value::Keyword(plain); + + assert!(n_v.as_keyword().is_some()); + assert!(n_v.as_plain_keyword().is_none()); + assert!(n_v.as_namespaced_keyword().is_some()); + + assert!(p_v.as_keyword().is_some()); + assert!(p_v.as_plain_keyword().is_some()); + assert!(p_v.as_namespaced_keyword().is_none()); + + assert!(n_v.clone().into_keyword().is_some()); + assert!(n_v.clone().into_plain_keyword().is_none()); + assert!(n_v.clone().into_namespaced_keyword().is_some()); + + assert!(p_v.clone().into_keyword().is_some()); + assert!(p_v.clone().into_plain_keyword().is_some()); + assert!(p_v.clone().into_namespaced_keyword().is_none()); + } } diff --git a/edn/tests/tests.rs b/edn/tests/tests.rs index 37d2013d..d9dc9380 100644 --- a/edn/tests/tests.rs +++ b/edn/tests/tests.rs @@ -1476,7 +1476,7 @@ fn test_is_and_as_type_helper_functions() { def_test_as_type!(value, as_text, i == 5, "hello world".to_string()); def_test_as_type!(value, as_symbol, i == 6, symbols::PlainSymbol::plain("$symbol")); def_test_as_type!(value, as_namespaced_symbol, i == 7, symbols::NamespacedSymbol::namespaced("$ns", "$symbol")); - def_test_as_type!(value, as_keyword, i == 8, symbols::Keyword::plain("hello")); + def_test_as_type!(value, as_plain_keyword, i == 8, symbols::Keyword::plain("hello")); def_test_as_type!(value, as_namespaced_keyword, i == 9, symbols::Keyword::namespaced("hello", "world")); def_test_as_type!(value, as_vector, i == 10, vec![Value::Integer(1)]); def_test_as_type!(value, as_list, i == 11, LinkedList::from_iter(vec![])); @@ -1494,7 +1494,7 @@ fn test_is_and_as_type_helper_functions() { def_test_into_type!(value, into_text, i == 5, "hello world".to_string()); def_test_into_type!(value, into_symbol, i == 6, symbols::PlainSymbol::plain("$symbol")); def_test_into_type!(value, into_namespaced_symbol, i == 7, symbols::NamespacedSymbol::namespaced("$ns", "$symbol")); - def_test_into_type!(value, into_keyword, i == 8, symbols::Keyword::plain("hello")); + def_test_into_type!(value, into_plain_keyword, i == 8, symbols::Keyword::plain("hello")); def_test_into_type!(value, into_namespaced_keyword, i == 9, symbols::Keyword::namespaced("hello", "world")); def_test_into_type!(value, into_vector, i == 10, vec![Value::Integer(1)]); def_test_into_type!(value, into_list, i == 11, LinkedList::from_iter(vec![])); diff --git a/parser-utils/src/value_and_span.rs b/parser-utils/src/value_and_span.rs index 82483055..5715442c 100644 --- a/parser-utils/src/value_and_span.rs +++ b/parser-utils/src/value_and_span.rs @@ -442,37 +442,54 @@ pub fn integer<'a>() -> Expected, fn(Stream<'a>) -> ParseRes parser(integer_ as fn(Stream<'a>) -> ParseResult>).expected("integer") } -pub fn namespaced_keyword_<'a>(input: Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>> { - satisfy_map(|v: &'a edn::ValueAndSpan| - v.inner.as_namespaced_keyword() - .and_then(|k| if k.is_namespaced() { Some(k) } else { None }) - ) +pub fn any_keyword_<'a>(input: Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>> { + satisfy_map(|v: &'a edn::ValueAndSpan| v.inner.as_keyword()) .parse_lazy(input) .into() } +pub fn namespaced_keyword_<'a>(input: Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>> { + satisfy_map(|v: &'a edn::ValueAndSpan| v.inner.as_namespaced_keyword()) + .parse_lazy(input) + .into() +} + +pub fn any_keyword<'a>() -> Expected, fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>>> { + parser(any_keyword_ as fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>).expected("any_keyword") +} + pub fn namespaced_keyword<'a>() -> Expected, fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>>> { parser(namespaced_keyword_ as fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>).expected("namespaced_keyword") } -pub fn forward_keyword_<'a>(input: Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>> { - satisfy_map(|v: &'a edn::ValueAndSpan| v.inner.as_namespaced_keyword().and_then(|k| if k.is_forward() && k.is_namespaced() { Some(k) } else { None })) +pub fn forward_any_keyword_<'a>(input: Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>> { + satisfy_map(|v: &'a edn::ValueAndSpan| v.inner.as_keyword().and_then(|k| if k.is_forward() { Some(k) } else { None })) .parse_lazy(input) .into() } -pub fn forward_keyword<'a>() -> Expected, fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>>> { - parser(forward_keyword_ as fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>).expected("forward_keyword") +pub fn forward_any_keyword<'a>() -> Expected, fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>>> { + parser(forward_any_keyword_ as fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>).expected("forward_any_keyword") } -pub fn backward_keyword_<'a>(input: Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>> { - satisfy_map(|v: &'a edn::ValueAndSpan| v.inner.as_namespaced_keyword().and_then(|k| if k.is_backward() && k.is_namespaced() { Some(k) } else { None })) +pub fn forward_namespaced_keyword_<'a>(input: Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>> { + satisfy_map(|v: &'a edn::ValueAndSpan| v.inner.as_namespaced_keyword().and_then(|k| if k.is_forward() { Some(k) } else { None })) .parse_lazy(input) .into() } -pub fn backward_keyword<'a>() -> Expected, fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>>> { - parser(backward_keyword_ as fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>).expected("backward_keyword") +pub fn forward_namespaced_keyword<'a>() -> Expected, fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>>> { + parser(forward_namespaced_keyword_ as fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>).expected("forward_namespaced_keyword") +} + +pub fn backward_namespaced_keyword_<'a>(input: Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>> { + satisfy_map(|v: &'a edn::ValueAndSpan| v.inner.as_namespaced_keyword().and_then(|k| if k.is_backward() { Some(k) } else { None })) + .parse_lazy(input) + .into() +} + +pub fn backward_namespaced_keyword<'a>() -> Expected, fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>>> { + parser(backward_namespaced_keyword_ as fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>).expected("backward_namespaced_keyword") } /// Generate a `satisfy` expression that matches a `PlainSymbol` value with the given name. @@ -570,7 +587,7 @@ macro_rules! keyword_map_parser { match input.uncons() { Ok(value) => { $( - if let Some(ref keyword) = value.inner.as_keyword() { + if let Some(ref keyword) = value.inner.as_plain_keyword() { if &keyword.name() == $keyword { if $tmp.is_some() { // Repeated match -- bail out! Providing good error diff --git a/query-parser/src/parse.rs b/query-parser/src/parse.rs index aabb7f64..49659632 100644 --- a/query-parser/src/parse.rs +++ b/query-parser/src/parse.rs @@ -46,7 +46,8 @@ use self::mentat_parser_utils::value_and_span::Stream as ValueStream; use self::mentat_parser_utils::value_and_span::{ Item, OfExactlyParsing, - forward_keyword, + forward_any_keyword, + forward_namespaced_keyword, keyword_map, list, map, @@ -306,12 +307,15 @@ def_parser!(Query, aggregate, Aggregate, { }); def_parser!(Query, pull_concrete_attribute_ident, PullConcreteAttribute, { - forward_keyword().map(|k| PullConcreteAttribute::Ident(::std::rc::Rc::new(k.clone()))) + forward_namespaced_keyword() + .map(|k| PullConcreteAttribute::Ident(::std::rc::Rc::new(k.clone()))) }); def_parser!(Query, pull_concrete_attribute, PullAttributeSpec, { (Query::pull_concrete_attribute_ident(), - optional(try(Query::alias_as().with(forward_keyword().map(|alias| ::std::rc::Rc::new(alias.clone())))))) + optional(try(Query::alias_as() + .with(forward_any_keyword() + .map(|alias| ::std::rc::Rc::new(alias.clone())))))) .map(|(attribute, alias)| PullAttributeSpec::Attribute( NamedPullAttribute { @@ -1216,6 +1220,7 @@ mod test { let foo_bar = ::std::rc::Rc::new(edn::Keyword::namespaced("foo", "bar")); let foo_baz = ::std::rc::Rc::new(edn::Keyword::namespaced("foo", "baz")); let foo_horse = ::std::rc::Rc::new(edn::Keyword::namespaced("foo", "horse")); + let horse = ::std::rc::Rc::new(edn::Keyword::plain("horse")); assert_edn_parses_to!(Query::pull_concrete_attribute, ":foo/bar", PullAttributeSpec::Attribute( @@ -1238,6 +1243,20 @@ mod test { PullConcreteAttribute::Ident(foo_baz.clone()).into()), ], })); + assert_edn_parses_to!(Find::elem, + "(pull ?v [:foo/bar :as :horse, :foo/baz])", + Element::Pull(Pull { + var: Variable::from_valid_name("?v"), + patterns: vec![ + PullAttributeSpec::Attribute( + NamedPullAttribute { + attribute: PullConcreteAttribute::Ident(foo_bar.clone()), + alias: Some(horse), + }), + PullAttributeSpec::Attribute( + PullConcreteAttribute::Ident(foo_baz.clone()).into()), + ], + })); assert_parse_failure_contains!(Find::elem, "(pull ?x [* :foo/bar])", r#"errors: [Unexpected(Borrowed("wildcard with specified attributes"))]"#); diff --git a/tests/pull.rs b/tests/pull.rs index c4923d09..a2c21f91 100644 --- a/tests/pull.rs +++ b/tests/pull.rs @@ -162,7 +162,7 @@ fn test_simple_pull() { // Execute a scalar query where the body is constant. // TODO: we shouldn't require `:where`; that makes this non-constant! - let query = r#"[:find (pull ?hood [:db/id :as :neighborhood/id + let query = r#"[:find (pull ?hood [:db/id :as :neighborhood :neighborhood/name]) . :in ?hood :where [?hood :neighborhood/district _]]"#; @@ -173,7 +173,7 @@ fn test_simple_pull() { let expected: StructuredMap = vec![ (kw!(:neighborhood/name), TypedValue::from("Beacon Hill")), - (kw!(:neighborhood/id), TypedValue::Ref(beacon)), + (kw!(:neighborhood), TypedValue::Ref(beacon)), ].into(); assert_eq!(result, expected.into());