rpp_parser/
parser.rs

1use nom::{
2    branch::alt,
3    bytes::complete::{take_till, take_till1},
4    character::complete::{char, line_ending, multispace0, none_of, one_of, space0, space1},
5    combinator::all_consuming,
6    multi::{many0, many_till},
7    sequence::{delimited, preceded, terminated, tuple},
8    Finish, Parser,
9};
10
11type Input<'a> = &'a str;
12
13type Result<'a, O = Input<'a>> = nom::IResult<Input<'a>, O>;
14
15#[derive(Debug, Clone)]
16pub struct Element<'a> {
17    /// The name of the element. E.g. `REAPER_PROJECT`, `TRACK`, `FXCHAIN`, `VST`, `CONTAINER`
18    pub tag: &'a str,
19    /// List of attributes for this element.
20    pub attr: Vec<&'a str>,
21    /// Children of this element. See [Child].
22    pub children: Vec<Child<'a>>,
23}
24
25#[derive(Debug, Clone)]
26pub enum Child<'a> {
27    /// An arbitrary line of text, split into a [String] list using RPP's string quoting rules.
28    Line(Vec<&'a str>),
29    /// A subelement.
30    Element(Element<'a>),
31}
32
33fn quoted_string(i: Input) -> Result {
34    let (i, quote_char) = one_of("\"'`")(i)?;
35    let (i, contents) = take_till(|x| x == quote_char || x == '\n' || x == '\r')(i)?;
36    let (i, _) = char(quote_char)(i)?;
37
38    Ok((i, contents))
39}
40
41fn unquoted_string(i: Input) -> Result {
42    // first character must not be quote
43    none_of("\"'`")(i)?;
44    // now take characters until we reach space / end of line
45    take_till1(|x| x == ' ' || x == '\n' || x == '\r')(i)
46}
47
48fn string(i: Input) -> Result {
49    alt((unquoted_string, quoted_string))(i)
50}
51
52fn string_list(i: Input) -> Result<Vec<&str>> {
53    // get first element
54    let (i, first_element) = string(i)?;
55
56    // get the rest
57    let (i, mut other_elements) = many0(preceded(space1, string))(i)?;
58
59    // prepend first element into list
60    // TODO: inserting is inefficient, think of way to improve this
61    other_elements.insert(0, first_element);
62
63    Ok((i, other_elements))
64}
65
66fn element_start(i: Input) -> Result<()> {
67    char('<').map(|_| ()).parse(i)
68}
69
70fn element_end(i: Input) -> Result<()> {
71    char('>').map(|_| ()).parse(i)
72}
73
74fn element_tag(i: Input) -> Result {
75    unquoted_string(i)
76}
77
78fn element(i: Input) -> Result<Element> {
79    // line starts with '<TAG'
80    let (i, _) = element_start(i)?;
81    let (i, tag) = element_tag(i)?;
82
83    // rest of line is attr
84    // (consumes newline)
85    let (i, attr) = terminated(
86        many0(preceded(space1, string)),
87        tuple((space0, line_ending)),
88    )(i)?;
89
90    let (i, (children, _end)) = many_till(
91        // keep taking child elements until end of element
92        delimited(
93            space0,
94            alt((element.map(Child::Element), string_list.map(Child::Line))),
95            tuple((space0, line_ending)),
96        ),
97        // element ends with a single line containing only '>'
98        // but this function isn't responsible for checking the newline
99        tuple((space0, element_end)),
100    )(i)?;
101
102    let element = Element {
103        tag,
104        attr,
105        children,
106    };
107
108    Ok((i, element))
109}
110
111/// Parse an RPP element into an [Element].
112pub fn parse_element(i: Input) -> std::result::Result<Element, nom::error::Error<Input>> {
113    all_consuming(delimited(multispace0, element, multispace0))(i)
114        .finish()
115        .map(|(_, element)| element)
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    mod quoted_string {
123        use super::*;
124
125        #[test]
126        fn test_01() {
127            let input = "'apple'";
128            let expected = "apple";
129            let (_, result) = quoted_string(input).unwrap();
130            assert_eq!(result, expected)
131        }
132
133        #[test]
134        fn test_02() {
135            let input = "'app\"le'";
136            let expected = "app\"le";
137            let (_, result) = quoted_string(input).unwrap();
138            assert_eq!(result, expected)
139        }
140
141        #[test]
142        fn test_03() {
143            let input = "\"asd asjhd basjh \"";
144            let expected = "asd asjhd basjh ";
145            let (_, result) = quoted_string(input).unwrap();
146            assert_eq!(result, expected)
147        }
148
149        #[test]
150        fn test_04() {
151            let input = "`asd asjhd basjh `";
152            let expected = "asd asjhd basjh ";
153            let (_, result) = quoted_string(input).unwrap();
154            assert_eq!(result, expected)
155        }
156
157        #[test]
158        fn test_05() {
159            let input = "`asd asjhd basjh ` 'hello'";
160            let expected = "asd asjhd basjh ";
161            let (_, result) = quoted_string(input).unwrap();
162            assert_eq!(result, expected)
163        }
164
165        #[test]
166        fn test_06() {
167            let input = "`asd asjhd b\nasjh ` 'hello'";
168            assert!(quoted_string(input).is_err())
169        }
170    }
171
172    #[cfg(test)]
173    mod unquoted_string {
174        use super::*;
175
176        #[test]
177        fn test_01() {
178            let input = "apple";
179            let expected = "apple";
180            let (_, result) = unquoted_string(input).unwrap();
181            assert_eq!(result, expected)
182        }
183
184        #[test]
185        fn test_02() {
186            let input = "hasquote''``' askjdla";
187            let expected = "hasquote''``'";
188            let (_, result) = unquoted_string(input).unwrap();
189            assert_eq!(result, expected)
190        }
191
192        #[test]
193        fn test_03() {
194            let input = "has space";
195            let expected = "has";
196            let (_, result) = unquoted_string(input).unwrap();
197            assert_eq!(result, expected)
198        }
199
200        #[test]
201        fn test_04() {
202            let input = "  leading space";
203            assert!(unquoted_string(input).is_err())
204        }
205
206        #[test]
207        fn test_05() {
208            let input = "'hello'";
209            assert!(unquoted_string(input).is_err())
210        }
211    }
212}