async-graphql/parser/src/parse/utils.rs

141 lines
3.7 KiB
Rust

use pest::iterators::{Pair, Pairs};
use super::Rule;
use crate::Result;
pub(super) fn next_if_rule<'a>(pairs: &mut Pairs<'a, Rule>, rule: Rule) -> Option<Pair<'a, Rule>> {
if pairs.peek().map_or(false, |pair| pair.as_rule() == rule) {
Some(pairs.next().unwrap())
} else {
None
}
}
pub(super) fn parse_if_rule<T>(
pairs: &mut Pairs<Rule>,
rule: Rule,
f: impl FnOnce(Pair<Rule>) -> Result<T>,
) -> Result<Option<T>> {
next_if_rule(pairs, rule).map(f).transpose()
}
pub(super) fn exactly_one<T>(iter: impl IntoIterator<Item = T>) -> T {
let mut iter = iter.into_iter();
let res = iter.next().unwrap();
debug_assert!(matches!(iter.next(), None));
res
}
pub(super) fn block_string_value(raw: &str) -> String {
// Split the string by either \r\n, \r or \n
let lines: Vec<_> = raw
.split("\r\n")
.flat_map(|s| s.split(['\r', '\n'].as_ref()))
.collect();
// Find the common indent
let common_indent = lines
.iter()
.skip(1)
.copied()
.filter_map(|line| line.find(|c| c != '\t' && c != ' '))
.min()
.unwrap_or(0);
let line_has_content = |line: &str| line.as_bytes().iter().any(|&c| c != b'\t' && c != b' ');
let first_contentful_line = lines
.iter()
.copied()
.position(line_has_content)
.unwrap_or(lines.len());
let ending_lines_start = lines
.iter()
.copied()
.rposition(line_has_content)
.map_or(0, |i| i + 1);
lines
.iter()
.copied()
.enumerate()
.take(ending_lines_start)
.skip(first_contentful_line)
// Remove the common indent, but not on the first line
.map(|(i, line)| {
if i != 0 && line.len() >= common_indent {
&line[common_indent..]
} else {
line
}
})
// Put a newline between each line
.enumerate()
.flat_map(|(i, line)| {
if i == 0 { [].as_ref() } else { ['\n'].as_ref() }
.iter()
.copied()
.chain(line.chars())
})
.collect()
}
#[test]
fn test_block_string_value() {
assert_eq!(block_string_value(""), "");
assert_eq!(block_string_value("\r\n"), "");
assert_eq!(block_string_value("\r\r\r\r\n\n\r\n\r\r"), "");
assert_eq!(block_string_value("abc"), "abc");
assert_eq!(
block_string_value("line 1\r\n line 2\n line 3\r line 4"),
"line 1\nline 2\n line 3\n line 4"
);
assert_eq!(
block_string_value("\r\r some text\r\n \n \n "),
"some text"
);
assert_eq!(
block_string_value(
r#"
a
b
c
"#
),
"a\nb\n\nc"
);
}
pub(super) fn string_value(s: &str) -> String {
let mut chars = s.chars();
std::iter::from_fn(|| {
Some(match chars.next()? {
'\\' => match chars.next().expect("backslash at end") {
c @ '\"' | c @ '\\' | c @ '/' => c,
'b' => '\x08',
'f' => '\x0C',
'n' => '\n',
'r' => '\r',
't' => '\t',
'u' => std::char::from_u32(
(0..4)
.map(|_| chars.next().unwrap().to_digit(16).unwrap())
.fold(0, |acc, digit| acc * 16 + digit),
)
.unwrap(),
_ => unreachable!(),
},
other => other,
})
})
.collect()
}
#[test]
fn test_string_value() {
assert_eq!(string_value("abc"), "abc");
assert_eq!(string_value("\\n\\b\\u2a1A"), "\n\x08\u{2A1A}");
assert_eq!(string_value("\\\"\\\\"), "\"\\");
}