dia_semver/semver/
parser.rs

1/*
2==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--
3
4Dia-Semver
5
6Copyright (C) 2018-2022  Anonymous
7
8There are several releases over multiple years,
9they are listed as ranges, such as: "2018-2022".
10
11This program is free software: you can redistribute it and/or modify
12it under the terms of the GNU Lesser General Public License as published by
13the Free Software Foundation, either version 3 of the License, or
14(at your option) any later version.
15
16This program is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU Lesser General Public License for more details.
20
21You should have received a copy of the GNU Lesser General Public License
22along with this program.  If not, see <https://www.gnu.org/licenses/>.
23
24::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
25*/
26
27use {
28    alloc::{
29        borrow::Cow,
30        string::{String, ToString},
31    },
32    core::{
33        cmp::Ordering,
34        iter::Skip,
35        str::{CharIndices, FromStr},
36    },
37    crate::{Error, Result, Semver},
38    super::parse_errors,
39};
40
41/// # Parser
42pub (super) struct Parser<'a> {
43
44    /// # String to be parsed
45    src: &'a str,
46
47    /// # Strict mode
48    strict: bool,
49
50    /// # Chars of source string
51    chars: Skip<CharIndices<'a>>,
52
53}
54
55impl<'a> Parser<'a> {
56
57    /// # Parses input string
58    pub fn parse(src: &'a str, strict: bool) -> Result<Semver> {
59        let src = if strict { src } else { src.trim() };
60
61        if src.is_empty() {
62            return Err(err!(parse_errors::EMPTY));
63        } else if src.len() > super::MAX_INPUT_STR_LEN{
64            return Err(err!(parse_errors::TOO_LARGE));
65        }
66
67        let mut parser = Self {
68            src: src,
69            strict,
70            chars: src.char_indices().skip(0),
71        };
72
73        // Major, minor, patch
74        let major = parser.parse_major_minor_patch_version(parse_errors::INVALID_MAJOR)?;
75        let minor = parser.parse_major_minor_patch_version(parse_errors::INVALID_MINOR)?;
76        let patch = parser.parse_major_minor_patch_version(parse_errors::INVALID_PATCH)?;
77
78        // Pre-release (optional)
79        let pre_release = match parser.parse_pre_release_or_build_metadata(super::PRE_RELEASE_STARTER) {
80            Ok(pre_release) => Ok(pre_release),
81            // Starter can be BUILD_METADATA_STARTER, we'll handle this error later
82            Err(err) if err.msg() == Some(parse_errors::INVALID_TOKEN) => Err(err),
83            Err(err) => return Err(err),
84        };
85
86        // Build metadata (optional)
87        let build_metadata = parser.parse_pre_release_or_build_metadata(super::BUILD_METADATA_STARTER)?;
88
89        // Pre-release
90        let pre_release = match pre_release {
91            Ok(pre_release) => pre_release,
92            // If there's no build metadata, pre-release must be some string or None -- otherwise it's invalid.
93            Err(err) if err.msg() == Some(parse_errors::INVALID_TOKEN) => {
94                match build_metadata {
95                    Some(_) => None,
96                    None => return Err(err),
97                }
98            },
99            Err(err) => return Err(err),
100        };
101
102        Ok(Semver {
103            major: major, minor: minor, patch: patch,
104            pre_release: pre_release, build_metadata: build_metadata,
105        })
106    }
107
108    /// # Parses major/minor/patch version
109    fn parse_major_minor_patch_version(&mut self, err_kind: &'static str) -> Result<u64> {
110        let err = || Err(Error::new(line!(), module_path!(), Some(Cow::Borrowed(err_kind))));
111
112        let mut skip_last_char = false;
113        let mut start_index: Option<usize> = None;
114        let mut end_index: Option<usize> = None;
115        let mut invalid_char_index: Option<usize> = None;
116        let mut first_char_is_zero = false;
117
118        for (index, chr) in &mut self.chars {
119            match chr {
120                '.' => match start_index {
121                    Some(_) => { skip_last_char = true; break; },
122                    None => return err(),
123                },
124                '0'..='9' => {
125                    if let Some(start_index) = start_index {
126                        if let Some(gap) = index.checked_sub(start_index) {
127                            if (gap > 0 && first_char_is_zero) || gap.cmp(&super::MAX_STR_LEN_OF_A_VERSION_NUMBER) != Ordering::Less {
128                                return err();
129                            }
130                        }
131                    } else {
132                        start_index = Some(index);
133                        first_char_is_zero = chr == '0';
134                    }
135                    end_index = Some(index);
136                },
137                _ => {
138                    invalid_char_index = Some(index);
139                    break;
140                },
141            };
142        }
143
144        match (start_index, end_index) {
145            (Some(start_index), Some(end_index)) if end_index >= start_index => match u64::from_str(&self.src[start_index..=end_index]) {
146                Ok(v) => {
147                    self.chars = self.src.char_indices().skip(
148                        end_index.checked_add(if skip_last_char { 2 } else { 1 }).unwrap_or(usize::max_value())
149                    );
150                    Ok(v)
151                },
152                Err(_) => err(),
153            },
154            _ => {
155                if let Some(index) = invalid_char_index { self.chars = self.src.char_indices().skip(index); }
156                match err_kind {
157                    parse_errors::INVALID_MINOR => if self.strict {
158                        match invalid_char_index {
159                            Some(_) => err(),
160                            None => Err(err!(parse_errors::MISSING_MINOR)),
161                        }
162                    } else {
163                        Ok(0)
164                    },
165                    parse_errors::INVALID_PATCH => if self.strict {
166                        match invalid_char_index {
167                            Some(_) => err(),
168                            None => Err(err!(parse_errors::MISSING_PATCH)),
169                        }
170                    } else {
171                        Ok(0)
172                    },
173                    _ => err(),
174                }
175            },
176        }
177    }
178
179    /// # Parses pre-release or build metadata
180    ///
181    /// `starter`: can be [`PRE_RELEASE_STARTER`][const:PRE_RELEASE_STARTER] or [`BUILD_METADATA_STARTER`][const:BUILD_METADATA_STARTER].
182    ///
183    /// [const:PRE_RELEASE_STARTER]: ../constant.PRE_RELEASE_STARTER.html
184    /// [const:BUILD_METADATA_STARTER]: ../constant.BUILD_METADATA_STARTER.html
185    fn parse_pre_release_or_build_metadata(&mut self, starter: char) -> Result<Option<String>> {
186        // Check starter and setup error kind
187        let err = match starter {
188            super::PRE_RELEASE_STARTER => Err(err!(parse_errors::INVALID_PRE_RELEASE)),
189            super::BUILD_METADATA_STARTER => Err(err!(parse_errors::INVALID_BUILD_METADATA)),
190            _ => return Err(err!(parse_errors::PARSER_ERROR)),
191        };
192
193        let mut start_index: Option<usize> = None;
194        let mut end_index: Option<usize> = None;
195        let mut first_char_is_zero: Option<bool> = None;
196        let mut last_char: Option<char> = None;
197        let mut all_are_numbers = starter == super::PRE_RELEASE_STARTER;
198
199        // Check token
200        match self.chars.next() {
201            Some((index, chr)) => if chr != starter {
202                self.chars = self.src.char_indices().skip(index);
203                return Err(err!(parse_errors::INVALID_TOKEN));
204            },
205            None => return Ok(None),
206        };
207
208        // Section 9: a pre-release numeric identifier MUST NOT include leading zeroes
209        for (index, chr) in &mut self.chars {
210            if start_index.is_none() { start_index = Some(index); }
211
212            match chr {
213                'a'..='z' | 'A'..='Z' | '-' => {
214                    end_index = Some(index);
215                    all_are_numbers = false;
216                },
217                '0'..='9' => {
218                    if starter == super::PRE_RELEASE_STARTER && matches!(last_char, None | Some('.')) {
219                        first_char_is_zero = Some(chr == '0');
220                    }
221                    end_index = Some(index);
222                },
223                // Pre-release is allowed to have build metadata go after it. But build metadata is not.
224                super::BUILD_METADATA_STARTER => if starter == super::PRE_RELEASE_STARTER { break; } else { return err; },
225                '.' => {
226                    if starter == super::PRE_RELEASE_STARTER {
227                        if all_are_numbers && first_char_is_zero.unwrap_or(false) {
228                            return err;
229                        }
230                        all_are_numbers = true;
231                        first_char_is_zero = None;
232                    }
233                    match last_char {
234                        Some(c) => match c {
235                            '.' | '-' => return err,
236                            _ => last_char = None,
237                        },
238                        None => return err,
239                    };
240                    continue;
241                },
242                _ => return err,
243            };
244
245            last_char = Some(chr);
246        }
247
248        match (last_char, start_index, end_index) {
249            (Some(last_char), Some(start_index), Some(end_index)) if last_char.is_ascii_alphanumeric() => {
250                if starter == super::PRE_RELEASE_STARTER && all_are_numbers && first_char_is_zero.unwrap_or(false) {
251                    return err;
252                }
253                self.chars = self.src.char_indices().skip(end_index.checked_add(1).unwrap_or(usize::max_value()));
254                Ok(Some(self.src[start_index..=end_index].to_string()))
255            },
256            _ => err,
257        }
258    }
259
260}