amarok_parser/builders/
helpers.rs1use amarok_syntax::{Diagnostic, Span};
2use pest::iterators::Pair;
3
4use crate::grammar::Rule;
5
6pub(crate) fn span_of(pair: &Pair<Rule>) -> Span {
7 let span = pair.as_span();
8 Span::new(span.start(), span.end())
9}
10
11pub(crate) fn expect_single_inner<'input>(
12 pair: Pair<'input, Rule>,
13 context: &str,
14) -> Result<Pair<'input, Rule>, Diagnostic> {
15 let span = span_of(&pair);
16 let mut inner = pair.into_inner();
17 let first = inner.next().ok_or_else(|| {
18 Diagnostic::new(format!("{context} had no inner content.")).with_span(span)
19 })?;
20 if inner.next().is_some() {
21 return Err(
22 Diagnostic::new(format!("{context} had more than one inner element.")).with_span(span),
23 );
24 }
25 Ok(first)
26}
27
28pub(crate) fn find_child<'input>(
33 pair: Pair<'input, Rule>,
34 rule: Rule,
35 context: &str,
36) -> Result<Pair<'input, Rule>, Diagnostic> {
37 let span = span_of(&pair);
38 pair.into_inner()
39 .find(|p| p.as_rule() == rule)
40 .ok_or_else(|| Diagnostic::new(format!("{context} missing {rule:?}.")).with_span(span))
41}
42
43pub(crate) fn collect_path_segments(
46 path_pair: Pair<Rule>,
47 context: &str,
48) -> Result<Vec<String>, Diagnostic> {
49 let path_span = span_of(&path_pair);
50 let mut path: Vec<String> = Vec::new();
51 for segment in path_pair.into_inner() {
52 if segment.as_rule() != Rule::identifier {
53 return Err(Diagnostic::new(format!(
54 "{context} expected identifier, got {:?}",
55 segment.as_rule()
56 ))
57 .with_span(span_of(&segment)));
58 }
59 path.push(segment.as_str().to_string());
60 }
61
62 if path.is_empty() {
63 return Err(Diagnostic::new(format!("{context} path was empty.")).with_span(path_span));
64 }
65
66 Ok(path)
67}
68
69pub(crate) fn unquote_string(text: &str, span: Span) -> Result<String, Diagnostic> {
70 if !text.starts_with('"') || !text.ends_with('"') || text.len() < 2 {
71 return Err(Diagnostic::new(format!("Invalid string literal: {text}")).with_span(span));
72 }
73
74 let content = &text[1..text.len() - 1];
75
76 let mut result = String::with_capacity(content.len());
78 let mut chars = content.chars();
79 while let Some(character) = chars.next() {
80 if character == '\\' {
81 let next = chars
82 .next()
83 .ok_or_else(|| Diagnostic::new("String ends with a backslash.").with_span(span))?;
84 match next {
85 '"' => result.push('"'),
86 '\\' => result.push('\\'),
87 other => {
88 return Err(Diagnostic::new(format!(
89 "Unsupported escape sequence: \\{other} (only \\\" and \\\\ supported)"
90 ))
91 .with_span(span));
92 }
93 }
94 } else {
95 result.push(character);
96 }
97 }
98
99 Ok(result)
100}