dia_semver/semver/
parser.rs1use {
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
41pub (super) struct Parser<'a> {
43
44 src: &'a str,
46
47 strict: bool,
49
50 chars: Skip<CharIndices<'a>>,
52
53}
54
55impl<'a> Parser<'a> {
56
57 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 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 let pre_release = match parser.parse_pre_release_or_build_metadata(super::PRE_RELEASE_STARTER) {
80 Ok(pre_release) => Ok(pre_release),
81 Err(err) if err.msg() == Some(parse_errors::INVALID_TOKEN) => Err(err),
83 Err(err) => return Err(err),
84 };
85
86 let build_metadata = parser.parse_pre_release_or_build_metadata(super::BUILD_METADATA_STARTER)?;
88
89 let pre_release = match pre_release {
91 Ok(pre_release) => pre_release,
92 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 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 fn parse_pre_release_or_build_metadata(&mut self, starter: char) -> Result<Option<String>> {
186 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 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 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 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}