Allow pull aliases to be non-namespaced.

This commit is contained in:
Richard Newman 2018-05-14 09:41:31 +01:00
parent 60cb5d2432
commit bf8f8c9954
6 changed files with 98 additions and 24 deletions

View file

@ -89,7 +89,7 @@ impl NamespaceableName {
} }
} }
fn dwim<N, T>(namespace: Option<N>, name: T) -> Self where N: AsRef<str>, T: AsRef<str> { fn new<N, T>(namespace: Option<N>, name: T) -> Self where N: AsRef<str>, T: AsRef<str> {
if let Some(ns) = namespace { if let Some(ns) = namespace {
Self::namespaced(ns, name) Self::namespaced(ns, name)
} else { } else {
@ -115,9 +115,9 @@ impl NamespaceableName {
let name = self.name(); let name = self.name();
if name.starts_with('_') { if name.starts_with('_') {
Self::dwim(self.namespace(), &name[1..]) Self::new(self.namespace(), &name[1..])
} else { } else {
Self::dwim(self.namespace(), &format!("_{}", name)) Self::new(self.namespace(), &format!("_{}", name))
} }
} }

View file

@ -346,6 +346,13 @@ macro_rules! def_common_value_methods {
def_as_ref!(as_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol); def_as_ref!(as_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol);
pub fn as_keyword(&self) -> Option<&symbols::Keyword> { 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 { match self {
&$t::Keyword(ref k) if !k.is_namespaced() => Some(k), &$t::Keyword(ref k) if !k.is_namespaced() => Some(k),
_ => None, _ => None,
@ -376,6 +383,13 @@ macro_rules! def_common_value_methods {
def_into!(into_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol,); def_into!(into_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol,);
pub fn into_keyword(self) -> Option<symbols::Keyword> { pub fn into_keyword(self) -> Option<symbols::Keyword> {
match self {
$t::Keyword(k) => Some(k),
_ => None,
}
}
pub fn into_plain_keyword(self) -> Option<symbols::Keyword> {
match self { match self {
$t::Keyword(k) => { $t::Keyword(k) => {
if !k.is_namespaced() { 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::Set(BTreeSet::new()).cmp(&Value::Set(BTreeSet::new())), Ordering::Equal);
assert_eq!(Value::Map(BTreeMap::new()).cmp(&Value::Map(BTreeMap::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());
}
} }

View file

@ -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_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_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_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_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_vector, i == 10, vec![Value::Integer(1)]);
def_test_as_type!(value, as_list, i == 11, LinkedList::from_iter(vec![])); 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_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_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_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_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_vector, i == 10, vec![Value::Integer(1)]);
def_test_into_type!(value, into_list, i == 11, LinkedList::from_iter(vec![])); def_test_into_type!(value, into_list, i == 11, LinkedList::from_iter(vec![]));

View file

@ -442,37 +442,54 @@ pub fn integer<'a>() -> Expected<FnParser<Stream<'a>, fn(Stream<'a>) -> ParseRes
parser(integer_ as fn(Stream<'a>) -> ParseResult<i64, Stream<'a>>).expected("integer") parser(integer_ as fn(Stream<'a>) -> ParseResult<i64, Stream<'a>>).expected("integer")
} }
pub fn namespaced_keyword_<'a>(input: Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>> { pub fn any_keyword_<'a>(input: Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>> {
satisfy_map(|v: &'a edn::ValueAndSpan| satisfy_map(|v: &'a edn::ValueAndSpan| v.inner.as_keyword())
v.inner.as_namespaced_keyword()
.and_then(|k| if k.is_namespaced() { Some(k) } else { None })
)
.parse_lazy(input) .parse_lazy(input)
.into() .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<FnParser<Stream<'a>, 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<FnParser<Stream<'a>, fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>>> { pub fn namespaced_keyword<'a>() -> Expected<FnParser<Stream<'a>, 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") 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>> { pub fn forward_any_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 })) satisfy_map(|v: &'a edn::ValueAndSpan| v.inner.as_keyword().and_then(|k| if k.is_forward() { Some(k) } else { None }))
.parse_lazy(input) .parse_lazy(input)
.into() .into()
} }
pub fn forward_keyword<'a>() -> Expected<FnParser<Stream<'a>, fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>>> { pub fn forward_any_keyword<'a>() -> Expected<FnParser<Stream<'a>, 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") 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>> { 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_backward() && k.is_namespaced() { Some(k) } else { None })) 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) .parse_lazy(input)
.into() .into()
} }
pub fn backward_keyword<'a>() -> Expected<FnParser<Stream<'a>, fn(Stream<'a>) -> ParseResult<&'a edn::Keyword, Stream<'a>>>> { pub fn forward_namespaced_keyword<'a>() -> Expected<FnParser<Stream<'a>, 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") 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<FnParser<Stream<'a>, 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. /// 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() { match input.uncons() {
Ok(value) => { 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 &keyword.name() == $keyword {
if $tmp.is_some() { if $tmp.is_some() {
// Repeated match -- bail out! Providing good error // Repeated match -- bail out! Providing good error

View file

@ -46,7 +46,8 @@ use self::mentat_parser_utils::value_and_span::Stream as ValueStream;
use self::mentat_parser_utils::value_and_span::{ use self::mentat_parser_utils::value_and_span::{
Item, Item,
OfExactlyParsing, OfExactlyParsing,
forward_keyword, forward_any_keyword,
forward_namespaced_keyword,
keyword_map, keyword_map,
list, list,
map, map,
@ -306,12 +307,15 @@ def_parser!(Query, aggregate, Aggregate, {
}); });
def_parser!(Query, pull_concrete_attribute_ident, PullConcreteAttribute, { 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, { def_parser!(Query, pull_concrete_attribute, PullAttributeSpec, {
(Query::pull_concrete_attribute_ident(), (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)| .map(|(attribute, alias)|
PullAttributeSpec::Attribute( PullAttributeSpec::Attribute(
NamedPullAttribute { NamedPullAttribute {
@ -1216,6 +1220,7 @@ mod test {
let foo_bar = ::std::rc::Rc::new(edn::Keyword::namespaced("foo", "bar")); 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_baz = ::std::rc::Rc::new(edn::Keyword::namespaced("foo", "baz"));
let foo_horse = ::std::rc::Rc::new(edn::Keyword::namespaced("foo", "horse")); 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, assert_edn_parses_to!(Query::pull_concrete_attribute,
":foo/bar", ":foo/bar",
PullAttributeSpec::Attribute( PullAttributeSpec::Attribute(
@ -1238,6 +1243,20 @@ mod test {
PullConcreteAttribute::Ident(foo_baz.clone()).into()), 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, assert_parse_failure_contains!(Find::elem,
"(pull ?x [* :foo/bar])", "(pull ?x [* :foo/bar])",
r#"errors: [Unexpected(Borrowed("wildcard with specified attributes"))]"#); r#"errors: [Unexpected(Borrowed("wildcard with specified attributes"))]"#);

View file

@ -162,7 +162,7 @@ fn test_simple_pull() {
// Execute a scalar query where the body is constant. // Execute a scalar query where the body is constant.
// TODO: we shouldn't require `:where`; that makes this non-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]) . :neighborhood/name]) .
:in ?hood :in ?hood
:where [?hood :neighborhood/district _]]"#; :where [?hood :neighborhood/district _]]"#;
@ -173,7 +173,7 @@ fn test_simple_pull() {
let expected: StructuredMap = vec![ let expected: StructuredMap = vec![
(kw!(:neighborhood/name), TypedValue::from("Beacon Hill")), (kw!(:neighborhood/name), TypedValue::from("Beacon Hill")),
(kw!(:neighborhood/id), TypedValue::Ref(beacon)), (kw!(:neighborhood), TypedValue::Ref(beacon)),
].into(); ].into();
assert_eq!(result, expected.into()); assert_eq!(result, expected.into());