141 lines
3.7 KiB
Rust
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("\\\"\\\\"), "\"\\");
|
|
}
|