Skip to main content

nom_kconfig/
string.rs

1use crate::{util::ws, KconfigInput};
2use nom::{
3    branch::alt,
4    bytes::complete::tag,
5    character::complete::{alphanumeric1, one_of},
6    combinator::{map, recognize},
7    error::{Error, ErrorKind, ParseError},
8    multi::many1,
9    sequence::delimited,
10    IResult, Input, Parser,
11};
12
13pub fn parse_string(input: KconfigInput) -> IResult<KconfigInput, String> {
14    map(
15        alt((
16            delimited(tag("'"), take_until_unbalanced('\''), tag("'")),
17            delimited(tag("\""), take_until_unbalanced('"'), tag("\"")),
18        )),
19        |d| d.fragment().to_string(),
20    )
21    .parse(input)
22}
23
24pub fn take_until_unbalanced(
25    delimiter: char,
26) -> impl Fn(KconfigInput) -> IResult<KconfigInput, KconfigInput> {
27    move |i: KconfigInput| {
28        let mut index: usize = 0;
29        let mut delimiter_counter = 0;
30
31        let end_of_line = match &i.find('\n') {
32            Some(e) => *e,
33            None => i.len(),
34        };
35
36        while let Some(n) = &i[index..end_of_line].find(delimiter) {
37            delimiter_counter += 1;
38            index += n + 1;
39        }
40
41        // we split just before the last double quote
42        match index.checked_sub(1) {
43            Some(i) => index = i,
44            None => {
45                return Err(nom::Err::Error(Error::from_error_kind(
46                    i,
47                    ErrorKind::TakeUntil,
48                )))
49            }
50        }
51        // Last delimiter is the string delimiter
52        delimiter_counter -= 1;
53
54        match delimiter_counter % 2 == 0 {
55            true => Ok(i.take_split(index)),
56            false => Err(nom::Err::Error(Error::from_error_kind(
57                i,
58                ErrorKind::TakeUntil,
59            ))),
60        }
61    }
62}
63
64/// A first word is `'something here'` or `"something here"` or just a normal word without spaces.
65/// It is used in places where Kconfig allows either a string or a symbol, such as in `default` attributes.
66pub fn parse_first_word(input: KconfigInput) -> IResult<KconfigInput, KconfigInput> {
67    alt((
68        recognize((tag("'"), take_until_first_word_end('\''), tag("'"))),
69        recognize((tag("\""), take_until_first_word_end('"'), tag("\""))),
70        recognize(ws(many1(alt((alphanumeric1, recognize(one_of("-._'\""))))))),
71    ))
72    .parse(input)
73}
74
75fn take_until_first_word_end(
76    delimiter: char,
77) -> impl Fn(KconfigInput) -> IResult<KconfigInput, KconfigInput> {
78    move |i: KconfigInput| {
79        let input = *i.fragment();
80        let end_of_line = input.find('\n').unwrap_or(input.len());
81        let search_start = 0;
82
83        if let Some(offset) = input[search_start..end_of_line].find(delimiter) {
84            let index = search_start + offset;
85            return Ok(i.take_split(index));
86        }
87
88        Err(nom::Err::Error(Error::from_error_kind(
89            i,
90            ErrorKind::TakeUntil,
91        )))
92    }
93}