]> git.proxmox.com Git - rustc.git/blame - vendor/semver/src/parse.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / vendor / semver / src / parse.rs
CommitLineData
3c0e092e
XL
1use crate::backport::*;
2use crate::error::{ErrorKind, Position};
3use crate::identifier::Identifier;
4use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq};
5use core::str::FromStr;
6
7/// Error parsing a SemVer version or version requirement.
8///
9/// # Example
10///
11/// ```
12/// use semver::Version;
13///
14/// fn main() {
15/// let err = Version::parse("1.q.r").unwrap_err();
16///
17/// // "unexpected character 'q' while parsing minor version number"
18/// eprintln!("{}", err);
19/// }
20/// ```
21pub struct Error {
22 pub(crate) kind: ErrorKind,
23}
24
25impl FromStr for Version {
26 type Err = Error;
27
28 fn from_str(text: &str) -> Result<Self, Self::Err> {
29 let mut pos = Position::Major;
30 let (major, text) = numeric_identifier(text, pos)?;
31 let text = dot(text, pos)?;
32
33 pos = Position::Minor;
34 let (minor, text) = numeric_identifier(text, pos)?;
35 let text = dot(text, pos)?;
36
37 pos = Position::Patch;
38 let (patch, text) = numeric_identifier(text, pos)?;
39
40 if text.is_empty() {
41 return Ok(Version::new(major, minor, patch));
42 }
43
44 let (pre, text) = if let Some(text) = text.strip_prefix('-') {
45 pos = Position::Pre;
46 let (pre, text) = prerelease_identifier(text)?;
47 if pre.is_empty() {
48 return Err(Error::new(ErrorKind::EmptySegment(pos)));
49 }
50 (pre, text)
51 } else {
52 (Prerelease::EMPTY, text)
53 };
54
55 let (build, text) = if let Some(text) = text.strip_prefix('+') {
56 pos = Position::Build;
57 let (build, text) = build_identifier(text)?;
58 if build.is_empty() {
59 return Err(Error::new(ErrorKind::EmptySegment(pos)));
60 }
61 (build, text)
62 } else {
63 (BuildMetadata::EMPTY, text)
64 };
65
66 if let Some(unexpected) = text.chars().next() {
67 return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
68 }
69
70 Ok(Version {
71 major,
72 minor,
73 patch,
74 pre,
75 build,
76 })
77 }
78}
79
80impl FromStr for VersionReq {
81 type Err = Error;
82
83 fn from_str(text: &str) -> Result<Self, Self::Err> {
84 let text = text.trim_start_matches(' ');
5099ac24
FG
85 if let Some((ch, text)) = wildcard(text) {
86 let rest = text.trim_start_matches(' ');
87 if rest.is_empty() {
3c0e092e
XL
88 #[cfg(not(no_const_vec_new))]
89 return Ok(VersionReq::STAR);
90 #[cfg(no_const_vec_new)] // rustc <1.39
91 return Ok(VersionReq {
92 comparators: Vec::new(),
93 });
5099ac24
FG
94 } else if rest.starts_with(',') {
95 return Err(Error::new(ErrorKind::WildcardNotTheOnlyComparator(ch)));
3c0e092e
XL
96 } else {
97 return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
98 }
99 }
100
101 let depth = 0;
102 let mut comparators = Vec::new();
103 let len = version_req(text, &mut comparators, depth)?;
104 unsafe { comparators.set_len(len) }
105 Ok(VersionReq { comparators })
106 }
107}
108
109impl FromStr for Comparator {
110 type Err = Error;
111
112 fn from_str(text: &str) -> Result<Self, Self::Err> {
113 let text = text.trim_start_matches(' ');
114 let (comparator, pos, rest) = comparator(text)?;
115 if !rest.is_empty() {
116 let unexpected = rest.chars().next().unwrap();
117 return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
118 }
119 Ok(comparator)
120 }
121}
122
123impl FromStr for Prerelease {
124 type Err = Error;
125
126 fn from_str(text: &str) -> Result<Self, Self::Err> {
127 let (pre, rest) = prerelease_identifier(text)?;
128 if !rest.is_empty() {
129 return Err(Error::new(ErrorKind::IllegalCharacter(Position::Pre)));
130 }
131 Ok(pre)
132 }
133}
134
135impl FromStr for BuildMetadata {
136 type Err = Error;
137
138 fn from_str(text: &str) -> Result<Self, Self::Err> {
139 let (build, rest) = build_identifier(text)?;
140 if !rest.is_empty() {
141 return Err(Error::new(ErrorKind::IllegalCharacter(Position::Build)));
142 }
143 Ok(build)
144 }
145}
146
147impl Error {
148 fn new(kind: ErrorKind) -> Self {
149 Error { kind }
150 }
151}
152
153impl Op {
154 const DEFAULT: Self = Op::Caret;
155}
156
157fn numeric_identifier(input: &str, pos: Position) -> Result<(u64, &str), Error> {
158 let mut len = 0;
159 let mut value = 0u64;
160
161 while let Some(&digit) = input.as_bytes().get(len) {
162 if digit < b'0' || digit > b'9' {
163 break;
164 }
165 if value == 0 && len > 0 {
166 return Err(Error::new(ErrorKind::LeadingZero(pos)));
167 }
168 match value
169 .checked_mul(10)
170 .and_then(|value| value.checked_add((digit - b'0') as u64))
171 {
172 Some(sum) => value = sum,
173 None => return Err(Error::new(ErrorKind::Overflow(pos))),
174 }
175 len += 1;
176 }
177
178 if len > 0 {
179 Ok((value, &input[len..]))
180 } else if let Some(unexpected) = input[len..].chars().next() {
181 Err(Error::new(ErrorKind::UnexpectedChar(pos, unexpected)))
182 } else {
183 Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
184 }
185}
186
5099ac24 187fn wildcard(input: &str) -> Option<(char, &str)> {
3c0e092e 188 if let Some(rest) = input.strip_prefix('*') {
5099ac24 189 Some(('*', rest))
3c0e092e 190 } else if let Some(rest) = input.strip_prefix('x') {
5099ac24 191 Some(('x', rest))
3c0e092e 192 } else if let Some(rest) = input.strip_prefix('X') {
5099ac24 193 Some(('X', rest))
3c0e092e
XL
194 } else {
195 None
196 }
197}
198
199fn dot(input: &str, pos: Position) -> Result<&str, Error> {
200 if let Some(rest) = input.strip_prefix('.') {
201 Ok(rest)
202 } else if let Some(unexpected) = input.chars().next() {
203 Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)))
204 } else {
205 Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
206 }
207}
208
209fn prerelease_identifier(input: &str) -> Result<(Prerelease, &str), Error> {
210 let (string, rest) = identifier(input, Position::Pre)?;
211 let identifier = unsafe { Identifier::new_unchecked(string) };
212 Ok((Prerelease { identifier }, rest))
213}
214
215fn build_identifier(input: &str) -> Result<(BuildMetadata, &str), Error> {
216 let (string, rest) = identifier(input, Position::Build)?;
217 let identifier = unsafe { Identifier::new_unchecked(string) };
218 Ok((BuildMetadata { identifier }, rest))
219}
220
221fn identifier(input: &str, pos: Position) -> Result<(&str, &str), Error> {
222 let mut accumulated_len = 0;
223 let mut segment_len = 0;
224 let mut segment_has_nondigit = false;
225
226 loop {
227 match input.as_bytes().get(accumulated_len + segment_len) {
228 Some(b'A'..=b'Z') | Some(b'a'..=b'z') | Some(b'-') => {
229 segment_len += 1;
230 segment_has_nondigit = true;
231 }
232 Some(b'0'..=b'9') => {
233 segment_len += 1;
234 }
235 boundary => {
236 if segment_len == 0 {
237 if accumulated_len == 0 && boundary != Some(&b'.') {
238 return Ok(("", input));
239 } else {
240 return Err(Error::new(ErrorKind::EmptySegment(pos)));
241 }
242 }
243 if pos == Position::Pre
244 && segment_len > 1
245 && !segment_has_nondigit
246 && input[accumulated_len..].starts_with('0')
247 {
248 return Err(Error::new(ErrorKind::LeadingZero(pos)));
249 }
250 accumulated_len += segment_len;
251 if boundary == Some(&b'.') {
252 accumulated_len += 1;
253 segment_len = 0;
254 segment_has_nondigit = false;
255 } else {
256 return Ok(input.split_at(accumulated_len));
257 }
258 }
259 }
260 }
261}
262
263fn op(input: &str) -> (Op, &str) {
264 let bytes = input.as_bytes();
923072b8 265 if bytes.first() == Some(&b'=') {
3c0e092e 266 (Op::Exact, &input[1..])
923072b8 267 } else if bytes.first() == Some(&b'>') {
3c0e092e
XL
268 if bytes.get(1) == Some(&b'=') {
269 (Op::GreaterEq, &input[2..])
270 } else {
271 (Op::Greater, &input[1..])
272 }
923072b8 273 } else if bytes.first() == Some(&b'<') {
3c0e092e
XL
274 if bytes.get(1) == Some(&b'=') {
275 (Op::LessEq, &input[2..])
276 } else {
277 (Op::Less, &input[1..])
278 }
923072b8 279 } else if bytes.first() == Some(&b'~') {
3c0e092e 280 (Op::Tilde, &input[1..])
923072b8 281 } else if bytes.first() == Some(&b'^') {
3c0e092e
XL
282 (Op::Caret, &input[1..])
283 } else {
284 (Op::DEFAULT, input)
285 }
286}
287
288fn comparator(input: &str) -> Result<(Comparator, Position, &str), Error> {
289 let (mut op, text) = op(input);
290 let default_op = input.len() == text.len();
291 let text = text.trim_start_matches(' ');
292
293 let mut pos = Position::Major;
294 let (major, text) = numeric_identifier(text, pos)?;
295 let mut has_wildcard = false;
296
297 let (minor, text) = if let Some(text) = text.strip_prefix('.') {
298 pos = Position::Minor;
5099ac24 299 if let Some((_, text)) = wildcard(text) {
3c0e092e
XL
300 has_wildcard = true;
301 if default_op {
302 op = Op::Wildcard;
303 }
304 (None, text)
305 } else {
306 let (minor, text) = numeric_identifier(text, pos)?;
307 (Some(minor), text)
308 }
309 } else {
310 (None, text)
311 };
312
313 let (patch, text) = if let Some(text) = text.strip_prefix('.') {
314 pos = Position::Patch;
5099ac24 315 if let Some((_, text)) = wildcard(text) {
3c0e092e
XL
316 if default_op {
317 op = Op::Wildcard;
318 }
319 (None, text)
320 } else if has_wildcard {
321 return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
322 } else {
323 let (patch, text) = numeric_identifier(text, pos)?;
324 (Some(patch), text)
325 }
326 } else {
327 (None, text)
328 };
329
330 let (pre, text) = if patch.is_some() && text.starts_with('-') {
331 pos = Position::Pre;
332 let text = &text[1..];
333 let (pre, text) = prerelease_identifier(text)?;
334 if pre.is_empty() {
335 return Err(Error::new(ErrorKind::EmptySegment(pos)));
336 }
337 (pre, text)
338 } else {
339 (Prerelease::EMPTY, text)
340 };
341
342 let text = if patch.is_some() && text.starts_with('+') {
343 pos = Position::Build;
344 let text = &text[1..];
345 let (build, text) = build_identifier(text)?;
346 if build.is_empty() {
347 return Err(Error::new(ErrorKind::EmptySegment(pos)));
348 }
349 text
350 } else {
351 text
352 };
353
354 let text = text.trim_start_matches(' ');
355
356 let comparator = Comparator {
357 op,
358 major,
359 minor,
360 patch,
361 pre,
362 };
363
364 Ok((comparator, pos, text))
365}
366
367fn version_req(input: &str, out: &mut Vec<Comparator>, depth: usize) -> Result<usize, Error> {
5099ac24
FG
368 let (comparator, pos, text) = match comparator(input) {
369 Ok(success) => success,
370 Err(mut error) => {
371 if let Some((ch, mut rest)) = wildcard(input) {
372 rest = rest.trim_start_matches(' ');
373 if rest.is_empty() || rest.starts_with(',') {
374 error.kind = ErrorKind::WildcardNotTheOnlyComparator(ch);
375 }
376 }
377 return Err(error);
378 }
379 };
3c0e092e
XL
380
381 if text.is_empty() {
382 out.reserve_exact(depth + 1);
383 unsafe { out.as_mut_ptr().add(depth).write(comparator) }
384 return Ok(depth + 1);
385 }
386
387 let text = if let Some(text) = text.strip_prefix(',') {
388 text.trim_start_matches(' ')
389 } else {
390 let unexpected = text.chars().next().unwrap();
391 return Err(Error::new(ErrorKind::ExpectedCommaFound(pos, unexpected)));
392 };
393
394 const MAX_COMPARATORS: usize = 32;
395 if depth + 1 == MAX_COMPARATORS {
396 return Err(Error::new(ErrorKind::ExcessiveComparators));
397 }
398
399 // Recurse to collect parsed Comparator objects on the stack. We perform a
400 // single allocation to allocate exactly the right sized Vec only once the
401 // total number of comparators is known.
402 let len = version_req(text, out, depth + 1)?;
403 unsafe { out.as_mut_ptr().add(depth).write(comparator) }
404 Ok(len)
405}