Skip to main content

yaml_subset/yaml/
parser.rs

1use std::fmt::Display;
2
3use super::{
4    string::{
5        DoubleQuotedStringEscapedChar, DoubleQuotedStringPart, SingleQuotedStringEscapedChar,
6        SingleQuotedStringPart,
7    },
8    AliasedYaml, ArrayData, BlockChomping, Document, DocumentData, HashData, HashElement, Yaml,
9};
10use pest_consume::{match_nodes, Error, Parser};
11
12#[derive(Debug)]
13pub struct YamlError(Error<Rule>);
14
15impl From<Error<Rule>> for YamlError {
16    fn from(err: Error<Rule>) -> Self {
17        YamlError(err)
18    }
19}
20
21impl Display for YamlError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(f, "{}", self.0)
24    }
25}
26
27impl YamlError {
28    pub fn location(&self) -> (usize, usize) {
29        match self.0.line_col {
30            pest::error::LineColLocation::Pos((line, col)) => (line, col),
31            pest::error::LineColLocation::Span((line, col), _) => (line, col),
32        }
33    }
34}
35
36pub type YamlResult<T> = std::result::Result<T, YamlError>;
37type InternalYamlResult<T> = std::result::Result<T, Error<Rule>>;
38pub type DocumentResult = YamlResult<Document>;
39type Node<'i> = pest_consume::Node<'i, Rule, ()>;
40
41#[derive(Parser)]
42#[grammar = "yaml.pest"]
43pub struct YamlParser;
44
45#[pest_consume::parser]
46impl YamlParser {
47    fn EOI(_input: Node) -> InternalYamlResult<()> {
48        Ok(())
49    }
50
51    fn comment_text(input: Node) -> InternalYamlResult<String> {
52        Ok(input.as_str().to_string())
53    }
54
55    fn comment(input: Node) -> InternalYamlResult<String> {
56        match_nodes!(input.into_children();
57            [comment_text(inner)] => Ok(inner),
58        )
59    }
60
61    fn commentnl(input: Node) -> InternalYamlResult<String> {
62        match_nodes!(input.into_children();
63            [comment_text(inner)] => Ok(inner),
64        )
65    }
66
67    fn alias(input: Node) -> InternalYamlResult<String> {
68        match_nodes!(input.into_children();
69            [alias_name(inner)] => Ok(inner),
70        )
71    }
72
73    fn alias_name(input: Node) -> InternalYamlResult<String> {
74        Ok(input.as_str().to_string())
75    }
76
77    fn anchor(input: Node) -> InternalYamlResult<Yaml> {
78        match_nodes!(input.into_children();
79            [alias_name(inner)] => Ok(Yaml::Anchor(inner)),
80        )
81    }
82
83    fn inline_hash_element(input: Node) -> InternalYamlResult<(String, Yaml)> {
84        match_nodes!(input.into_children();
85        [hash_key(k), inline_array_value(v)] => {
86
87            Ok((k, v))
88        })
89    }
90
91    fn inline_hash(input: Node) -> InternalYamlResult<Yaml> {
92        match_nodes!(input.into_children();
93        [inline_hash_element(v), inline_hash_element(vs)..] => {
94            let mut values = vec![v];
95            values.extend(vs);
96            Ok(Yaml::InlineHash(values))
97        },
98        [] => Ok(Yaml::InlineHash(vec![])))
99    }
100
101    fn unquoted_string(input: Node) -> InternalYamlResult<Yaml> {
102        Ok(Yaml::UnquotedString(
103            input.as_str().to_string().trim_end_matches(" ").to_string(),
104        ))
105    }
106
107    fn unquoted_inline_string(input: Node) -> InternalYamlResult<Yaml> {
108        Ok(Yaml::UnquotedString(
109            input.as_str().to_string().trim_end_matches(" ").to_string(),
110        ))
111    }
112
113    fn escaped_double_quote_char_value(
114        input: Node,
115    ) -> InternalYamlResult<DoubleQuotedStringEscapedChar> {
116        match input.as_str().chars().next().unwrap() {
117            'n' => Ok(DoubleQuotedStringEscapedChar::Newline),
118            '\n' => Ok(DoubleQuotedStringEscapedChar::RealNewline),
119            'r' => Ok(DoubleQuotedStringEscapedChar::CarriageReturn),
120            't' => Ok(DoubleQuotedStringEscapedChar::Tab),
121            '\\' => Ok(DoubleQuotedStringEscapedChar::Backslash),
122            '"' => Ok(DoubleQuotedStringEscapedChar::Quote),
123            _ => unreachable!(),
124        }
125    }
126
127    fn double_quote_text(input: Node) -> InternalYamlResult<String> {
128        Ok(input.as_str().to_string())
129    }
130
131    fn removable_newline(_input: Node) -> InternalYamlResult<()> {
132        Ok(())
133    }
134
135    fn blank_lines(input: Node) -> InternalYamlResult<usize> {
136        Ok(input.as_str().lines().count() - 1)
137    }
138
139    fn double_quote_without_escape(input: Node) -> InternalYamlResult<DoubleQuotedStringPart> {
140        match_nodes!(input.into_children();
141            [escaped_double_quote_char_value(inner)] => Ok(DoubleQuotedStringPart::EscapedChar(inner)),
142            [double_quote_text(inner)] => Ok(DoubleQuotedStringPart::String(inner)),
143            [blank_lines(inner)] => Ok(DoubleQuotedStringPart::BlankLines(inner)),
144            [removable_newline(_)] => Ok(DoubleQuotedStringPart::RemovableNewline),
145        )
146    }
147
148    fn double_quote_content(input: Node) -> InternalYamlResult<Vec<DoubleQuotedStringPart>> {
149        match_nodes!(input.into_children();
150            [double_quote_without_escape(v)..] => Ok(v.collect()),
151        )
152    }
153
154    fn double_quoted_string(input: Node) -> InternalYamlResult<Yaml> {
155        match_nodes!(input.into_children();
156            [double_quote_content(v)] => Ok(Yaml::DoubleQuotedString(v)),
157        )
158    }
159
160    fn single_quote(input: Node) -> InternalYamlResult<SingleQuotedStringEscapedChar> {
161        match input.as_str().chars().next().unwrap() {
162            '\'' => Ok(SingleQuotedStringEscapedChar::SingleQuote),
163            _ => unreachable!(),
164        }
165    }
166
167    fn single_quote_text(input: Node) -> InternalYamlResult<String> {
168        Ok(input.as_str().to_string())
169    }
170
171    fn escaped_single_quote_char(input: Node) -> InternalYamlResult<SingleQuotedStringPart> {
172        match_nodes!(input.into_children();
173            [single_quote(inner)] => Ok(SingleQuotedStringPart::EscapedChar(inner)),
174            [single_quote_text(inner)] => Ok(SingleQuotedStringPart::String(inner)),
175            [blank_lines(inner)] => Ok(SingleQuotedStringPart::BlankLines(inner)),
176            [removable_newline(_)] => Ok(SingleQuotedStringPart::RemovableNewline),
177        )
178    }
179
180    fn single_quote_content(input: Node) -> InternalYamlResult<Vec<SingleQuotedStringPart>> {
181        match_nodes!(input.into_children();
182            [escaped_single_quote_char(v)..] => Ok(v.collect()),
183        )
184    }
185
186    fn single_quoted_string(input: Node) -> InternalYamlResult<Yaml> {
187        match_nodes!(input.into_children();
188            [single_quote_content(v)] => Ok(Yaml::SingleQuotedString(v)),
189        )
190    }
191
192    fn inline_array_string(input: Node) -> InternalYamlResult<Yaml> {
193        match_nodes!(input.into_children();
194            [unquoted_inline_string(value)] => Ok(value),
195            [double_quoted_string(value)] => Ok(value),
196            [single_quoted_string(value)] => Ok(value))
197    }
198
199    fn inline_array_value(input: Node) -> InternalYamlResult<Yaml> {
200        match_nodes!(input.into_children();
201            [anchor(value)] => Ok(value),
202            [inline_array(value)] => Ok(value),
203            [inline_hash(value)] => Ok(value),
204            [inline_array_string(value)] => Ok(value))
205    }
206
207    fn inline_array(input: Node) -> InternalYamlResult<Yaml> {
208        match_nodes!(input.into_children();
209        [inline_array_value(v), inline_array_value(vs)..] => {
210            let mut values = vec![v];
211            values.extend(vs);
212            Ok(Yaml::InlineArray(values))
213        },
214        [] => Ok(Yaml::InlineArray(vec![])))
215    }
216
217    fn string(input: Node) -> InternalYamlResult<Yaml> {
218        match_nodes!(input.into_children();
219            [unquoted_string(value)] => Ok(value),
220            [double_quoted_string(value)] => Ok(value),
221            [single_quoted_string(value)] => Ok(value))
222    }
223
224    fn inline_value(input: Node) -> InternalYamlResult<Yaml> {
225        match_nodes!(input.into_children();
226            [anchor(value)] => Ok(value),
227            [inline_array(value)] => Ok(value),
228            [inline_hash(value)] => Ok(value),
229            [string(value)] => Ok(value))
230    }
231
232    fn block_string(input: Node) -> InternalYamlResult<String> {
233        Ok(input.as_str().to_string())
234    }
235
236    fn block_empty_lines(input: Node) -> InternalYamlResult<Vec<String>> {
237        Ok((0..input.as_str().len())
238            .into_iter()
239            .map(|_| "".to_string())
240            .collect())
241    }
242
243    fn string_block_chomping(input: Node) -> InternalYamlResult<BlockChomping> {
244        Ok(match input.as_str() {
245            "+" => BlockChomping::Keep,
246            "-" => BlockChomping::Strip,
247            _ => unreachable!(),
248        })
249    }
250
251    fn string_multiline_indent(input: Node) -> InternalYamlResult<Option<usize>> {
252        Ok(if input.as_str().is_empty() {
253            None
254        } else {
255            let given: usize = input.as_str().parse().unwrap();
256            Some(given - 1)
257        })
258    }
259
260    fn string_multiline_content_part(input: Node) -> InternalYamlResult<Vec<String>> {
261        match_nodes!(input.into_children();
262        [block_empty_lines(es), block_string(b)] => {
263            Ok(es.into_iter().chain(std::iter::once(b)).collect())
264        })
265    }
266
267    fn string_multiline_trailing(input: Node) -> InternalYamlResult<Vec<String>> {
268        Ok(input.as_str().chars().filter(|c| *c == '\n').map(|_| String::new()).collect())
269    }
270
271    fn string_multiline_content(input: Node) -> InternalYamlResult<Vec<String>> {
272        match_nodes!(input.into_children();
273        [block_empty_lines(es), block_string(b), string_multiline_content_part(bs).., string_multiline_trailing(trailing)] => {
274            Ok(es.into_iter()
275                .chain(std::iter::once(b))
276                .chain(bs.into_iter().flat_map(|x| x))
277                .chain(trailing)
278                .collect())
279        })
280    }
281
282    fn string_multiline_folded(input: Node) -> InternalYamlResult<Yaml> {
283        match_nodes!(input.into_children();
284        [string_multiline_indent(indent), string_multiline_content(cs)] => Ok(Yaml::FoldedString(
285            clean_multiline_strings(indent, cs), BlockChomping::Clip
286        )),
287        [string_block_chomping(chomp), string_multiline_indent(indent), string_multiline_content(cs)] => Ok(Yaml::FoldedString(
288            clean_multiline_strings(indent, cs), chomp
289        )))
290
291        // string_block_chomping
292    }
293
294    fn string_multiline_literal(input: Node) -> InternalYamlResult<Yaml> {
295        match_nodes!(input.into_children();
296        [string_multiline_indent(indent), string_multiline_content(cs)] => Ok(Yaml::LiteralString(
297            clean_multiline_strings(indent, cs), BlockChomping::Clip
298        )),
299        [string_block_chomping(chomp), string_multiline_indent(indent), string_multiline_content(cs)] => Ok(Yaml::LiteralString(
300            clean_multiline_strings(indent, cs), chomp
301        )))
302    }
303
304    fn yaml_value(input: Node) -> InternalYamlResult<Yaml> {
305        match_nodes!(input.into_children();
306            [hash(value)] => Ok(value),
307            [array(value)] => Ok(value),
308            [string_multiline_literal(value)] => Ok(value),
309            [string_multiline_folded(value)] => Ok(value),
310            [inline_value(value)] => Ok(value))
311    }
312
313    fn aliased_yaml_value(input: Node) -> InternalYamlResult<AliasedYaml> {
314        match_nodes!(input.into_children();
315        [alias(alias), yaml_value(val)] => Ok(AliasedYaml {
316            alias: Some(alias),
317            value: val,
318        }),
319        [yaml_value(val)] => Ok(AliasedYaml {
320            alias: None,
321            value: val,
322        }))
323    }
324
325    fn block_array_aliased_yaml_value(input: Node) -> InternalYamlResult<AliasedYaml> {
326        match_nodes!(input.into_children();
327            [alternative_aliased_yaml_value(value)] => Ok(value),
328            [aliased_yaml_value(value)] => Ok(value))
329    }
330
331    fn alternative_aliased_yaml_value(input: Node) -> InternalYamlResult<AliasedYaml> {
332        match_nodes!(input.into_children();
333        [alias(alias), alternative_hash(val)] => Ok(AliasedYaml {
334            alias: Some(alias),
335            value: val,
336        }),
337        [alternative_hash(val)] => Ok(AliasedYaml {
338            alias: None,
339            value: val,
340        }),
341        [alias(alias), alternative_array(val)] => Ok(AliasedYaml {
342            alias: Some(alias),
343            value: val,
344        }),
345        [alternative_array(val)] => Ok(AliasedYaml {
346            alias: None,
347            value: val,
348        }))
349    }
350
351    fn hash_key(input: Node) -> InternalYamlResult<String> {
352        Ok(input.as_str().trim_end().to_string())
353    }
354
355    fn hash_element(input: Node) -> InternalYamlResult<HashElement> {
356        //println!("{:#?}", input);
357        match_nodes!(input.into_children();
358                [hash_key(key), block_array_aliased_yaml_value(value)] => Ok(HashElement { key, value }),
359        )
360    }
361
362    fn first_hash_element(input: Node) -> InternalYamlResult<Vec<HashData>> {
363        //println!("{:#?}", input);
364
365        match_nodes!(input.into_children();
366            [hash_element(element), comment(c)] => Ok(vec![HashData::Element(element), HashData::InlineComment(c)]),
367            [hash_element(element)] => Ok(vec![HashData::Element(element)]),
368
369        )
370    }
371
372    fn hash_element_data(input: Node) -> InternalYamlResult<Vec<HashData>> {
373        //println!("{:#?}", input);
374
375        match_nodes!(input.into_children();
376            [commentnls(cs), hash_element(element), comment(c)] => Ok(
377                cs.into_iter().map(|c| HashData::Comment(c))
378                .chain(vec![HashData::Element(element), HashData::InlineComment(c)].into_iter())
379                .collect(),
380            ),
381            [commentnls(cs), hash_element(element)] => Ok(
382                cs.into_iter().map(|c| HashData::Comment(c))
383                .chain(vec![HashData::Element(element)].into_iter())
384                .collect(),
385            ),
386        )
387    }
388
389    fn hash(input: Node) -> InternalYamlResult<Yaml> {
390        //println!("{:#?}", input);
391        match_nodes!(input.into_children();
392            [commentnls(cs), first_hash_element(element1), hash_element_data(elements).., commentnls(cs2)] => Ok(
393                Yaml::Hash(
394                    cs.into_iter().map(|c| HashData::Comment(c))
395                    .chain(element1.into_iter())
396                    .chain(elements.into_iter().flatten())
397                    .chain(cs2.into_iter().map(|c| HashData::Comment(c)))
398                    .collect()
399                )
400            ),
401        )
402    }
403
404    fn alternative_hash_continuation(input: Node) -> InternalYamlResult<Vec<HashData>> {
405        match_nodes!(input.into_children();
406            [commentnls(cs), first_hash_element(element), hash_element_data(elements)..] => Ok(
407                cs.into_iter().map(|c| HashData::Comment(c))
408                .chain(element.into_iter())
409                .chain(elements.into_iter().flatten())
410                .collect()
411            ),
412        )
413    }
414
415    fn alternative_hash(input: Node) -> InternalYamlResult<Yaml> {
416        match_nodes!(input.into_children();
417            [first_hash_element(element1), alternative_hash_continuation(cont), commentnls(cs)] => Ok(
418                Yaml::Hash(
419                    element1.into_iter()
420                    .chain(cont.into_iter())
421                    .chain(cs.into_iter().map(|c| HashData::Comment(c)))
422                    .collect()
423                )
424            ),
425            [first_hash_element(element1), commentnls(cs)] => Ok(
426                Yaml::Hash(
427                    element1.into_iter()
428                    .chain(cs.into_iter().map(|c| HashData::Comment(c)))
429                    .collect()
430                )
431            ),
432        )
433    }
434
435    fn block_array_element(input: Node) -> InternalYamlResult<ArrayData> {
436        match_nodes!(input.into_children();
437            [block_array_aliased_yaml_value(value)] => Ok(ArrayData::Element(value)),
438        )
439    }
440
441    fn first_block_array_element(input: Node) -> InternalYamlResult<Vec<ArrayData>> {
442        match_nodes!(input.into_children();
443            [block_array_element(element), comment(c)] => Ok(vec![element, ArrayData::InlineComment(c)]),
444            [block_array_element(element)] => Ok(vec![element]),
445        )
446    }
447
448    fn block_array_data(input: Node) -> InternalYamlResult<Vec<ArrayData>> {
449        match_nodes!(input.into_children();
450            [commentnls(cs), block_array_element(element), comment(c)] => Ok(
451                cs.into_iter().map(|c| ArrayData::Comment(c))
452                .chain(vec![element, ArrayData::InlineComment(c)].into_iter())
453                .collect(),
454            ),
455            [commentnls(cs), block_array_element(element)] => Ok(
456                cs.into_iter().map(|c| ArrayData::Comment(c))
457                .chain(vec![element].into_iter())
458                .collect(),
459            ),
460        )
461    }
462
463    fn array(input: Node) -> InternalYamlResult<Yaml> {
464        match_nodes!(input.into_children();
465            [inline_array(val)] => Ok(val),
466            [block_array(val)] => Ok(val)
467        )
468    }
469
470    fn block_array(input: Node) -> InternalYamlResult<Yaml> {
471        match_nodes!(input.into_children();
472        [commentnls(cs),first_block_array_element(element1), block_array_data(elements).., commentnls(cs2)] => Ok(
473            Yaml::Array(
474                cs.into_iter().map(|c| ArrayData::Comment(c))
475                .chain(element1.into_iter())
476                .chain(elements.into_iter().flatten())
477                .chain(cs2.into_iter().map(|c| ArrayData::Comment(c)))
478                .collect()
479            )
480        ))
481    }
482
483    fn alternative_array(input: Node) -> InternalYamlResult<Yaml> {
484        match_nodes!(input.into_children();
485        [first_block_array_element(element1),first_block_array_element(element2), block_array_data(elements).., commentnls(cs)] => Ok(
486            Yaml::Array(
487                element1.into_iter()
488                .chain(element2.into_iter())
489                .chain(elements.into_iter().flatten())
490                .chain(cs.into_iter().map(|c| ArrayData::Comment(c)))
491                .collect()
492            )
493        ))
494    }
495
496    fn commentnls(input: Node) -> InternalYamlResult<Vec<String>> {
497        //println!("{:?}", input);
498        match_nodes!(
499            input.into_children();
500            [commentnl(cs)..] => Ok(
501                cs.collect()
502
503            )
504        )
505    }
506
507    fn leading_comments(input: Node) -> InternalYamlResult<Vec<String>> {
508        //println!("{:?}", input);
509        match_nodes!(
510            input.into_children();
511            [commentnl(cs)..] => Ok(
512                cs.collect()
513
514            )
515        )
516    }
517
518    fn document(input: Node) -> InternalYamlResult<Yaml> {
519        match_nodes!(input.into_children();
520        [hash(h)] => Ok(h),
521        [array(a)] => Ok(a))
522    }
523
524    fn yaml(input: Node) -> InternalYamlResult<Document> {
525        //println!("{:#?}"  , input);
526        match_nodes!(input.into_children();
527        [leading_comments(before), commentnls(cs), document(val), comment(cs2).., EOI(_)] => Ok(
528            Document {
529                leading_comments: before,
530                items: cs.into_iter().map(|c| DocumentData::Comment(c))
531                .chain(vec![DocumentData::Yaml(val)].into_iter())
532                .chain(cs2.into_iter().map(|c| DocumentData::Comment(c))).collect(),
533            }
534        ))
535    }
536}
537
538pub fn parse_yaml_file(input_str: &str) -> YamlResult<Document> {
539    // Parse the input into `Nodes`
540    let inputs = YamlParser::parse(Rule::yaml, input_str)?;
541    // There should be a single root node in the parsed tree
542    let input = inputs.single()?;
543    // Consume the `Node` recursively into the final value
544    YamlParser::yaml(input).map_err(|e| e.into())
545}
546
547fn count_whitespace_bytes_at_start(input: &str) -> usize {
548    input
549        .chars()
550        .take_while(|ch| ch.is_whitespace() && *ch != '\n')
551        .map(|ch| ch.len_utf8())
552        .sum()
553}
554
555fn clean_multiline_strings(indent: Option<usize>, lines: Vec<String>) -> Vec<String> {
556    let indent = indent.unwrap_or_else(|| count_whitespace_bytes_at_start(&lines[0]));
557
558    lines
559        .into_iter()
560        .map(|x| {
561            if x.is_empty() {
562                x
563            } else {
564                x[indent..].to_string()
565            }
566        })
567        .collect()
568}