Skip to main content

debian_control/
relations.rs

1//! Parsing of Debian relations strings.
2use std::iter::Peekable;
3use std::str::Chars;
4
5/// Build profile for a package.
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
7pub enum BuildProfile {
8    /// A build profile that is enabled.
9    Enabled(String),
10
11    /// A build profile that is disabled.
12    Disabled(String),
13}
14
15impl std::fmt::Display for BuildProfile {
16    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
17        match self {
18            BuildProfile::Enabled(s) => f.write_str(s),
19            BuildProfile::Disabled(s) => write!(f, "!{}", s),
20        }
21    }
22}
23
24impl std::str::FromStr for BuildProfile {
25    type Err = String;
26
27    fn from_str(s: &str) -> Result<Self, Self::Err> {
28        if let Some(s) = s.strip_prefix('!') {
29            Ok(BuildProfile::Disabled(s.to_string()))
30        } else {
31            Ok(BuildProfile::Enabled(s.to_string()))
32        }
33    }
34}
35
36/// Constraint on a Debian package version.
37#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
38pub enum VersionConstraint {
39    /// <<
40    LessThan, // <<
41    /// <=
42    LessThanEqual, // <=
43    /// =
44    Equal, // =
45    /// >>
46    GreaterThan, // >>
47    /// >=
48    GreaterThanEqual, // >=
49}
50
51impl std::str::FromStr for VersionConstraint {
52    type Err = String;
53
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        match s {
56            ">=" => Ok(VersionConstraint::GreaterThanEqual),
57            "<=" => Ok(VersionConstraint::LessThanEqual),
58            "=" => Ok(VersionConstraint::Equal),
59            ">>" => Ok(VersionConstraint::GreaterThan),
60            "<<" => Ok(VersionConstraint::LessThan),
61            _ => Err(format!("Invalid version constraint: {}", s)),
62        }
63    }
64}
65
66impl std::fmt::Display for VersionConstraint {
67    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
68        match self {
69            VersionConstraint::GreaterThanEqual => f.write_str(">="),
70            VersionConstraint::LessThanEqual => f.write_str("<="),
71            VersionConstraint::Equal => f.write_str("="),
72            VersionConstraint::GreaterThan => f.write_str(">>"),
73            VersionConstraint::LessThan => f.write_str("<<"),
74        }
75    }
76}
77
78/// Let's start with defining all kinds of tokens and
79/// composite nodes.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
81#[allow(non_camel_case_types)]
82#[repr(u16)]
83#[allow(missing_docs)]
84pub enum SyntaxKind {
85    IDENT = 0, // package name
86    COLON,     // :
87    PIPE,
88    COMMA,      // ,
89    L_PARENS,   // (
90    R_PARENS,   // )
91    L_BRACKET,  // [
92    R_BRACKET,  // ]
93    NOT,        // !
94    L_ANGLE,    // <
95    R_ANGLE,    // >
96    EQUAL,      // =
97    WHITESPACE, // whitespace
98    NEWLINE,    // newline
99    DOLLAR,     // $
100    L_CURLY,
101    R_CURLY,
102    COMMENT, // comment line starting with #
103    ERROR,   // as well as errors
104
105    // composite nodes
106    ROOT,       // The entire file
107    ENTRY,      // A single entry
108    RELATION,   // An alternative in a dependency
109    ARCHQUAL,   // An architecture qualifier
110    VERSION,    // A version constraint
111    CONSTRAINT, // (">=", "<=", "=", ">>", "<<")
112    ARCHITECTURES,
113    PROFILES,
114    SUBSTVAR,
115}
116
117/// Convert our `SyntaxKind` into the rowan `SyntaxKind`.
118#[cfg(feature = "lossless")]
119impl From<SyntaxKind> for rowan::SyntaxKind {
120    fn from(kind: SyntaxKind) -> Self {
121        Self(kind as u16)
122    }
123}
124
125/// A lexer for relations strings.
126pub struct Lexer<'a> {
127    input: Peekable<Chars<'a>>,
128}
129
130impl<'a> Lexer<'a> {
131    /// Create a new lexer for the given input.
132    pub fn new(input: &'a str) -> Self {
133        Lexer {
134            input: input.chars().peekable(),
135        }
136    }
137
138    fn is_whitespace(c: char) -> bool {
139        c == ' ' || c == '\t' || c == '\r'
140    }
141
142    fn is_valid_ident_char(c: char) -> bool {
143        c.is_ascii_alphanumeric() || c == '-' || c == '.' || c == '+' || c == '~'
144    }
145
146    fn read_while<F>(&mut self, predicate: F) -> String
147    where
148        F: Fn(char) -> bool,
149    {
150        let mut result = String::new();
151        while let Some(&c) = self.input.peek() {
152            if predicate(c) {
153                result.push(c);
154                self.input.next();
155            } else {
156                break;
157            }
158        }
159        result
160    }
161
162    fn next_token(&mut self) -> Option<(SyntaxKind, String)> {
163        if let Some(&c) = self.input.peek() {
164            match c {
165                ':' => {
166                    self.input.next();
167                    Some((SyntaxKind::COLON, c.to_string()))
168                }
169                '|' => {
170                    self.input.next();
171                    Some((SyntaxKind::PIPE, c.to_string()))
172                }
173                ',' => {
174                    self.input.next();
175                    Some((SyntaxKind::COMMA, c.to_string()))
176                }
177                '(' => {
178                    self.input.next();
179                    Some((SyntaxKind::L_PARENS, c.to_string()))
180                }
181                ')' => {
182                    self.input.next();
183                    Some((SyntaxKind::R_PARENS, c.to_string()))
184                }
185                '[' => {
186                    self.input.next();
187                    Some((SyntaxKind::L_BRACKET, c.to_string()))
188                }
189                ']' => {
190                    self.input.next();
191                    Some((SyntaxKind::R_BRACKET, c.to_string()))
192                }
193                '!' => {
194                    self.input.next();
195                    Some((SyntaxKind::NOT, c.to_string()))
196                }
197                '$' => {
198                    self.input.next();
199                    Some((SyntaxKind::DOLLAR, c.to_string()))
200                }
201                '{' => {
202                    self.input.next();
203                    Some((SyntaxKind::L_CURLY, c.to_string()))
204                }
205                '}' => {
206                    self.input.next();
207                    Some((SyntaxKind::R_CURLY, c.to_string()))
208                }
209                '<' => {
210                    self.input.next();
211                    Some((SyntaxKind::L_ANGLE, c.to_string()))
212                }
213                '>' => {
214                    self.input.next();
215                    Some((SyntaxKind::R_ANGLE, c.to_string()))
216                }
217                '=' => {
218                    self.input.next();
219                    Some((SyntaxKind::EQUAL, c.to_string()))
220                }
221                '\n' => {
222                    self.input.next();
223                    Some((SyntaxKind::NEWLINE, c.to_string()))
224                }
225                '#' => {
226                    let comment = self.read_while(|c| c != '\n');
227                    Some((SyntaxKind::COMMENT, comment))
228                }
229                _ if Self::is_whitespace(c) => {
230                    let whitespace = self.read_while(Self::is_whitespace);
231                    Some((SyntaxKind::WHITESPACE, whitespace))
232                }
233                // TODO: separate handling for package names and versions?
234                _ if Self::is_valid_ident_char(c) => {
235                    let key = self.read_while(Self::is_valid_ident_char);
236                    Some((SyntaxKind::IDENT, key))
237                }
238                _ => {
239                    self.input.next();
240                    Some((SyntaxKind::ERROR, c.to_string()))
241                }
242            }
243        } else {
244            None
245        }
246    }
247}
248
249impl Iterator for Lexer<'_> {
250    type Item = (SyntaxKind, String);
251
252    fn next(&mut self) -> Option<Self::Item> {
253        self.next_token()
254    }
255}
256
257pub(crate) fn lex(input: &str) -> Vec<(SyntaxKind, String)> {
258    let mut lexer = Lexer::new(input);
259    lexer.by_ref().collect::<Vec<_>>()
260}