use nom::bytes::complete::tag;
use nom::combinator::map;
use nom_locate::LocatedSpan;
use shape::location::Location;
use shape::location::SourceId;
use super::ParseResult;
use crate::connectors::ConnectSpec;
pub(crate) type Span<'a> = LocatedSpan<&'a str, SpanExtra>;
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub(crate) struct SpanExtra {
pub(super) spec: ConnectSpec,
pub(super) errors: Vec<(String, usize)>,
pub(super) local_vars: Vec<String>,
}
#[cfg(test)]
pub(crate) fn new_span(input: &str) -> Span<'_> {
Span::new_extra(
input,
SpanExtra {
spec: super::JSONSelection::default_connect_spec(),
local_vars: Vec::new(),
errors: Vec::new(),
},
)
}
pub(crate) fn new_span_with_spec(input: &str, spec: ConnectSpec) -> Span<'_> {
Span::new_extra(
input,
SpanExtra {
spec,
local_vars: Vec::new(),
errors: Vec::new(),
},
)
}
pub(super) fn get_connect_spec(input: &Span) -> ConnectSpec {
input.extra.spec
}
impl SpanExtra {
pub(super) fn is_local_var(&self, name: &String) -> bool {
self.local_vars.contains(name)
}
pub(super) fn with_local_var(mut self, name: String) -> Self {
if !self.local_vars.contains(&name) {
self.local_vars.push(name);
}
self
}
}
pub(crate) trait Ranged {
fn range(&self) -> OffsetRange;
fn shape_location(&self, source_id: &SourceId) -> Option<Location> {
self.range().map(|range| source_id.location(range))
}
}
pub(crate) type OffsetRange = Option<std::ops::Range<usize>>;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WithRange<T> {
node: Box<T>,
range: OffsetRange,
}
impl<T> std::ops::Deref for WithRange<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.node.as_ref()
}
}
impl<T> std::ops::DerefMut for WithRange<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.node.as_mut()
}
}
impl<T> AsRef<T> for WithRange<T> {
fn as_ref(&self) -> &T {
self.node.as_ref()
}
}
impl<T> AsMut<T> for WithRange<T> {
fn as_mut(&mut self) -> &mut T {
self.node.as_mut()
}
}
impl<T> PartialEq<T> for WithRange<T>
where
T: PartialEq,
{
fn eq(&self, other: &T) -> bool {
self.node.as_ref() == other
}
}
impl<T: std::hash::Hash> std::hash::Hash for WithRange<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.node.as_ref().hash(state)
}
}
impl<T> Ranged for WithRange<T> {
fn range(&self) -> OffsetRange {
self.range.clone()
}
}
impl<T> WithRange<T> {
pub(crate) fn new(node: T, range: OffsetRange) -> Self {
Self {
node: Box::new(node),
range,
}
}
#[allow(unused)]
pub(crate) fn take(self) -> T {
*self.node
}
pub(crate) fn take_as<U>(self, f: impl FnOnce(T) -> U) -> WithRange<U> {
WithRange::new(f(*self.node), self.range)
}
}
pub(super) fn merge_ranges(left: OffsetRange, right: OffsetRange) -> OffsetRange {
match (left, right) {
(Some(left_range), Some(right_range)) => {
Some(left_range.start.min(right_range.start)..left_range.end.max(right_range.end))
}
(Some(left_range), None) => Some(left_range),
(None, Some(right_range)) => Some(right_range),
(None, None) => None,
}
}
pub(super) fn ranged_span<'a, 'b: 'a>(
s: &'a str,
) -> impl FnMut(Span<'b>) -> ParseResult<'b, WithRange<&'b str>> {
map(tag(s), |t: Span<'b>| {
let start = t.location_offset();
let range = Some(start..start + s.len());
WithRange::new(*t.fragment(), range)
})
}
#[cfg(test)]
pub(crate) mod strip_ranges {
use apollo_compiler::collections::IndexMap;
use super::super::known_var::KnownVariable;
use super::super::lit_expr::LitExpr;
use super::super::lit_expr::LitOp;
use super::super::parser::*;
use super::WithRange;
pub(crate) trait StripRanges {
fn strip_ranges(&self) -> Self;
}
impl StripRanges for WithRange<String> {
fn strip_ranges(&self) -> Self {
WithRange::new(self.as_ref().clone(), None)
}
}
impl StripRanges for WithRange<LitOp> {
fn strip_ranges(&self) -> Self {
WithRange::new(self.as_ref().clone(), None)
}
}
impl StripRanges for JSONSelection {
fn strip_ranges(&self) -> Self {
match &self.inner {
TopLevelSelection::Named(subselect) => Self {
inner: TopLevelSelection::Named(subselect.strip_ranges()),
spec: self.spec,
},
TopLevelSelection::Path(path) => Self {
inner: TopLevelSelection::Path(path.strip_ranges()),
spec: self.spec,
},
}
}
}
impl StripRanges for NamedSelection {
fn strip_ranges(&self) -> Self {
Self {
prefix: match &self.prefix {
NamingPrefix::None => NamingPrefix::None,
NamingPrefix::Alias(alias) => NamingPrefix::Alias(alias.strip_ranges()),
NamingPrefix::Spread(_) => NamingPrefix::Spread(None),
},
path: self.path.strip_ranges(),
}
}
}
impl StripRanges for PathSelection {
fn strip_ranges(&self) -> Self {
Self {
path: self.path.strip_ranges(),
}
}
}
impl StripRanges for WithRange<PathList> {
fn strip_ranges(&self) -> Self {
WithRange::new(
match self.as_ref() {
PathList::Var(var, rest) => {
PathList::Var(var.strip_ranges(), rest.strip_ranges())
}
PathList::Key(key, rest) => {
PathList::Key(key.strip_ranges(), rest.strip_ranges())
}
PathList::Expr(expr, rest) => {
PathList::Expr(expr.strip_ranges(), rest.strip_ranges())
}
PathList::Method(method, opt_args, rest) => PathList::Method(
method.strip_ranges(),
opt_args.as_ref().map(|args| args.strip_ranges()),
rest.strip_ranges(),
),
PathList::Question(tail) => PathList::Question(tail.strip_ranges()),
PathList::Selection(sub) => PathList::Selection(sub.strip_ranges()),
PathList::Empty => PathList::Empty,
},
None,
)
}
}
impl StripRanges for SubSelection {
fn strip_ranges(&self) -> Self {
SubSelection {
selections: self.selections.iter().map(|s| s.strip_ranges()).collect(),
..Default::default()
}
}
}
impl StripRanges for Alias {
fn strip_ranges(&self) -> Self {
Alias {
name: self.name.strip_ranges(),
range: None,
}
}
}
impl StripRanges for WithRange<Key> {
fn strip_ranges(&self) -> Self {
WithRange::new(self.as_ref().clone(), None)
}
}
impl StripRanges for MethodArgs {
fn strip_ranges(&self) -> Self {
MethodArgs {
args: self.args.iter().map(|arg| arg.strip_ranges()).collect(),
range: None,
}
}
}
impl StripRanges for WithRange<LitExpr> {
fn strip_ranges(&self) -> Self {
WithRange::new(
match self.as_ref() {
LitExpr::String(s) => LitExpr::String(s.clone()),
LitExpr::Number(n) => LitExpr::Number(n.clone()),
LitExpr::Bool(b) => LitExpr::Bool(*b),
LitExpr::Null => LitExpr::Null,
LitExpr::Object(map) => {
let mut new_map = IndexMap::default();
for (key, value) in map {
new_map.insert(key.strip_ranges(), value.strip_ranges());
}
LitExpr::Object(new_map)
}
LitExpr::Array(vec) => {
let mut new_vec = vec![];
for value in vec {
new_vec.push(value.strip_ranges());
}
LitExpr::Array(new_vec)
}
LitExpr::Path(path) => LitExpr::Path(path.strip_ranges()),
LitExpr::LitPath(literal, subpath) => {
LitExpr::LitPath(literal.strip_ranges(), subpath.strip_ranges())
}
LitExpr::OpChain(op, operands) => LitExpr::OpChain(
op.strip_ranges(),
operands
.iter()
.map(|operand| operand.strip_ranges())
.collect(),
),
},
None,
)
}
}
impl StripRanges for WithRange<KnownVariable> {
fn strip_ranges(&self) -> Self {
WithRange::new(self.as_ref().clone(), None)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::assert_debug_snapshot;
use crate::assert_snapshot;
use crate::connectors::JSONSelection;
#[test]
fn test_merge_ranges() {
assert_eq!(merge_ranges(None, None), None);
assert_eq!(merge_ranges(Some(0..1), None), Some(0..1));
assert_eq!(merge_ranges(None, Some(0..1)), Some(0..1));
assert_eq!(merge_ranges(Some(0..1), Some(1..2)), Some(0..2));
assert_eq!(merge_ranges(Some(1..2), Some(0..1)), Some(0..2));
assert_eq!(merge_ranges(Some(0..1), Some(1..2)), Some(0..2));
assert_eq!(merge_ranges(Some(0..2), Some(1..3)), Some(0..3));
assert_eq!(merge_ranges(Some(1..3), Some(0..2)), Some(0..3));
}
#[test]
fn test_arrow_path_ranges() {
let parsed = JSONSelection::parse(" __typename: @ -> echo ( \"Frog\" , ) ").unwrap();
assert_debug_snapshot!(parsed);
}
#[test]
fn test_parse_with_range_snapshots() {
let parsed = JSONSelection::parse(
r#"
path: some.nested.path { isbn author { name }}
alias: "not an identifier" {
# Inject "Frog" as the __typename
__typename: @->echo( "Frog" , )
wrapped: $->echo({ wrapped : @ , })
group: { a b c }
arg: $args . arg
field
}
"#,
)
.unwrap();
assert_snapshot!(format!("{:#?}", parsed));
}
}