crystal_cif_io/grammar/strings_textfields/
unquoted_string.rs

1use std::fmt::Display;
2
3use winnow::{
4    combinator::{alt, repeat},
5    error::StrContext,
6    stream::AsChar,
7    PResult, Parser,
8};
9
10use crate::grammar::{
11    character_sets::{Eol, NonBlankChar, NotEol, OrdinaryChar},
12    SyntacticUnit,
13};
14
15#[derive(Debug, Clone)]
16pub struct UnquotedString {
17    content: String,
18}
19
20impl AsRef<str> for UnquotedString {
21    #[inline]
22    fn as_ref(&self) -> &str {
23        <String as AsRef<str>>::as_ref(&self.content)
24    }
25}
26
27impl UnquotedString {
28    pub fn new(content: String) -> Self {
29        Self { content }
30    }
31}
32
33fn not_eol_unquoted(input: &mut &str) -> PResult<UnquotedString> {
34    (
35        NotEol::parser
36            .verify(|c| c.char() != '_')
37            .context(winnow::error::StrContext::Expected(
38                winnow::error::StrContextValue::Description("Unquoted string without '_' prefix"),
39            )),
40        alt((OrdinaryChar::parser.map(|oc| oc.as_char()), ';')),
41        repeat::<_, _, String, _, _>(0.., NonBlankChar::parser),
42    )
43        .map(|(_not_eol, c, content)| UnquotedString::new(format!("{c}{content}")))
44        .parse_next(input)
45}
46
47fn eol_unquoted(input: &mut &str) -> PResult<UnquotedString> {
48    (
49        Eol::parser.context(StrContext::Label("Leading <EOL> for an <UnquotedString>")),
50        OrdinaryChar::parser.context(StrContext::Label("<OrdinaryChar>")),
51        repeat::<_, _, String, _, _>(0.., NonBlankChar::parser),
52    )
53        .map(|(_eol, oc, content)| UnquotedString::new(format!("{oc}{content}")))
54        .parse_next(input)
55}
56
57pub fn pure_unquoted(input: &mut &str) -> PResult<UnquotedString> {
58    (
59        OrdinaryChar::parser.context(StrContext::Label("<OrdinaryChar>")),
60        repeat::<_, _, String, _, _>(0.., NonBlankChar::parser),
61    )
62        .map(|(oc, content)| UnquotedString::new(format!("{oc}{content}")))
63        .parse_next(input)
64}
65
66impl SyntacticUnit for UnquotedString {
67    type ParseResult = Self;
68
69    type FormatOutput = String;
70
71    fn parser(input: &mut &str) -> winnow::prelude::PResult<Self::ParseResult> {
72        alt((not_eol_unquoted, eol_unquoted)).parse_next(input)
73    }
74
75    fn formatted_output(&self) -> Self::FormatOutput {
76        self.content.to_owned()
77    }
78}
79
80impl Display for UnquotedString {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        write!(f, "{}", self.formatted_output())
83    }
84}
85
86#[cfg(test)]
87mod test {
88    use crate::grammar::{whitespace_comments::WhiteSpace, SyntacticUnit};
89
90    use super::UnquotedString;
91
92    #[test]
93    fn unquoted_string() {
94        let mut input = " rm # known chiral centre
95";
96        let value = UnquotedString::parser(&mut input).unwrap();
97        let white_space = WhiteSpace::parser(&mut input).unwrap();
98        println!("{value}{white_space}");
99    }
100}