diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/rust/semver/src/parse.rs | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/third_party/rust/semver/src/parse.rs b/third_party/rust/semver/src/parse.rs new file mode 100644 index 0000000000..6a8f6ffba4 --- /dev/null +++ b/third_party/rust/semver/src/parse.rs @@ -0,0 +1,405 @@ +use crate::backport::*; +use crate::error::{ErrorKind, Position}; +use crate::identifier::Identifier; +use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq}; +use core::str::FromStr; + +/// Error parsing a SemVer version or version requirement. +/// +/// # Example +/// +/// ``` +/// use semver::Version; +/// +/// fn main() { +/// let err = Version::parse("1.q.r").unwrap_err(); +/// +/// // "unexpected character 'q' while parsing minor version number" +/// eprintln!("{}", err); +/// } +/// ``` +pub struct Error { + pub(crate) kind: ErrorKind, +} + +impl FromStr for Version { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let mut pos = Position::Major; + let (major, text) = numeric_identifier(text, pos)?; + let text = dot(text, pos)?; + + pos = Position::Minor; + let (minor, text) = numeric_identifier(text, pos)?; + let text = dot(text, pos)?; + + pos = Position::Patch; + let (patch, text) = numeric_identifier(text, pos)?; + + if text.is_empty() { + return Ok(Version::new(major, minor, patch)); + } + + let (pre, text) = if let Some(text) = text.strip_prefix('-') { + pos = Position::Pre; + let (pre, text) = prerelease_identifier(text)?; + if pre.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + (pre, text) + } else { + (Prerelease::EMPTY, text) + }; + + let (build, text) = if let Some(text) = text.strip_prefix('+') { + pos = Position::Build; + let (build, text) = build_identifier(text)?; + if build.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + (build, text) + } else { + (BuildMetadata::EMPTY, text) + }; + + if let Some(unexpected) = text.chars().next() { + return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected))); + } + + Ok(Version { + major, + minor, + patch, + pre, + build, + }) + } +} + +impl FromStr for VersionReq { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let text = text.trim_start_matches(' '); + if let Some((ch, text)) = wildcard(text) { + let rest = text.trim_start_matches(' '); + if rest.is_empty() { + #[cfg(not(no_const_vec_new))] + return Ok(VersionReq::STAR); + #[cfg(no_const_vec_new)] // rustc <1.39 + return Ok(VersionReq { + comparators: Vec::new(), + }); + } else if rest.starts_with(',') { + return Err(Error::new(ErrorKind::WildcardNotTheOnlyComparator(ch))); + } else { + return Err(Error::new(ErrorKind::UnexpectedAfterWildcard)); + } + } + + let depth = 0; + let mut comparators = Vec::new(); + let len = version_req(text, &mut comparators, depth)?; + unsafe { comparators.set_len(len) } + Ok(VersionReq { comparators }) + } +} + +impl FromStr for Comparator { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let text = text.trim_start_matches(' '); + let (comparator, pos, rest) = comparator(text)?; + if !rest.is_empty() { + let unexpected = rest.chars().next().unwrap(); + return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected))); + } + Ok(comparator) + } +} + +impl FromStr for Prerelease { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let (pre, rest) = prerelease_identifier(text)?; + if !rest.is_empty() { + return Err(Error::new(ErrorKind::IllegalCharacter(Position::Pre))); + } + Ok(pre) + } +} + +impl FromStr for BuildMetadata { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let (build, rest) = build_identifier(text)?; + if !rest.is_empty() { + return Err(Error::new(ErrorKind::IllegalCharacter(Position::Build))); + } + Ok(build) + } +} + +impl Error { + fn new(kind: ErrorKind) -> Self { + Error { kind } + } +} + +impl Op { + const DEFAULT: Self = Op::Caret; +} + +fn numeric_identifier(input: &str, pos: Position) -> Result<(u64, &str), Error> { + let mut len = 0; + let mut value = 0u64; + + while let Some(&digit) = input.as_bytes().get(len) { + if digit < b'0' || digit > b'9' { + break; + } + if value == 0 && len > 0 { + return Err(Error::new(ErrorKind::LeadingZero(pos))); + } + match value + .checked_mul(10) + .and_then(|value| value.checked_add((digit - b'0') as u64)) + { + Some(sum) => value = sum, + None => return Err(Error::new(ErrorKind::Overflow(pos))), + } + len += 1; + } + + if len > 0 { + Ok((value, &input[len..])) + } else if let Some(unexpected) = input[len..].chars().next() { + Err(Error::new(ErrorKind::UnexpectedChar(pos, unexpected))) + } else { + Err(Error::new(ErrorKind::UnexpectedEnd(pos))) + } +} + +fn wildcard(input: &str) -> Option<(char, &str)> { + if let Some(rest) = input.strip_prefix('*') { + Some(('*', rest)) + } else if let Some(rest) = input.strip_prefix('x') { + Some(('x', rest)) + } else if let Some(rest) = input.strip_prefix('X') { + Some(('X', rest)) + } else { + None + } +} + +fn dot(input: &str, pos: Position) -> Result<&str, Error> { + if let Some(rest) = input.strip_prefix('.') { + Ok(rest) + } else if let Some(unexpected) = input.chars().next() { + Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected))) + } else { + Err(Error::new(ErrorKind::UnexpectedEnd(pos))) + } +} + +fn prerelease_identifier(input: &str) -> Result<(Prerelease, &str), Error> { + let (string, rest) = identifier(input, Position::Pre)?; + let identifier = unsafe { Identifier::new_unchecked(string) }; + Ok((Prerelease { identifier }, rest)) +} + +fn build_identifier(input: &str) -> Result<(BuildMetadata, &str), Error> { + let (string, rest) = identifier(input, Position::Build)?; + let identifier = unsafe { Identifier::new_unchecked(string) }; + Ok((BuildMetadata { identifier }, rest)) +} + +fn identifier(input: &str, pos: Position) -> Result<(&str, &str), Error> { + let mut accumulated_len = 0; + let mut segment_len = 0; + let mut segment_has_nondigit = false; + + loop { + match input.as_bytes().get(accumulated_len + segment_len) { + Some(b'A'..=b'Z') | Some(b'a'..=b'z') | Some(b'-') => { + segment_len += 1; + segment_has_nondigit = true; + } + Some(b'0'..=b'9') => { + segment_len += 1; + } + boundary => { + if segment_len == 0 { + if accumulated_len == 0 && boundary != Some(&b'.') { + return Ok(("", input)); + } else { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + } + if pos == Position::Pre + && segment_len > 1 + && !segment_has_nondigit + && input[accumulated_len..].starts_with('0') + { + return Err(Error::new(ErrorKind::LeadingZero(pos))); + } + accumulated_len += segment_len; + if boundary == Some(&b'.') { + accumulated_len += 1; + segment_len = 0; + segment_has_nondigit = false; + } else { + return Ok(input.split_at(accumulated_len)); + } + } + } + } +} + +fn op(input: &str) -> (Op, &str) { + let bytes = input.as_bytes(); + if bytes.first() == Some(&b'=') { + (Op::Exact, &input[1..]) + } else if bytes.first() == Some(&b'>') { + if bytes.get(1) == Some(&b'=') { + (Op::GreaterEq, &input[2..]) + } else { + (Op::Greater, &input[1..]) + } + } else if bytes.first() == Some(&b'<') { + if bytes.get(1) == Some(&b'=') { + (Op::LessEq, &input[2..]) + } else { + (Op::Less, &input[1..]) + } + } else if bytes.first() == Some(&b'~') { + (Op::Tilde, &input[1..]) + } else if bytes.first() == Some(&b'^') { + (Op::Caret, &input[1..]) + } else { + (Op::DEFAULT, input) + } +} + +fn comparator(input: &str) -> Result<(Comparator, Position, &str), Error> { + let (mut op, text) = op(input); + let default_op = input.len() == text.len(); + let text = text.trim_start_matches(' '); + + let mut pos = Position::Major; + let (major, text) = numeric_identifier(text, pos)?; + let mut has_wildcard = false; + + let (minor, text) = if let Some(text) = text.strip_prefix('.') { + pos = Position::Minor; + if let Some((_, text)) = wildcard(text) { + has_wildcard = true; + if default_op { + op = Op::Wildcard; + } + (None, text) + } else { + let (minor, text) = numeric_identifier(text, pos)?; + (Some(minor), text) + } + } else { + (None, text) + }; + + let (patch, text) = if let Some(text) = text.strip_prefix('.') { + pos = Position::Patch; + if let Some((_, text)) = wildcard(text) { + if default_op { + op = Op::Wildcard; + } + (None, text) + } else if has_wildcard { + return Err(Error::new(ErrorKind::UnexpectedAfterWildcard)); + } else { + let (patch, text) = numeric_identifier(text, pos)?; + (Some(patch), text) + } + } else { + (None, text) + }; + + let (pre, text) = if patch.is_some() && text.starts_with('-') { + pos = Position::Pre; + let text = &text[1..]; + let (pre, text) = prerelease_identifier(text)?; + if pre.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + (pre, text) + } else { + (Prerelease::EMPTY, text) + }; + + let text = if patch.is_some() && text.starts_with('+') { + pos = Position::Build; + let text = &text[1..]; + let (build, text) = build_identifier(text)?; + if build.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + text + } else { + text + }; + + let text = text.trim_start_matches(' '); + + let comparator = Comparator { + op, + major, + minor, + patch, + pre, + }; + + Ok((comparator, pos, text)) +} + +fn version_req(input: &str, out: &mut Vec<Comparator>, depth: usize) -> Result<usize, Error> { + let (comparator, pos, text) = match comparator(input) { + Ok(success) => success, + Err(mut error) => { + if let Some((ch, mut rest)) = wildcard(input) { + rest = rest.trim_start_matches(' '); + if rest.is_empty() || rest.starts_with(',') { + error.kind = ErrorKind::WildcardNotTheOnlyComparator(ch); + } + } + return Err(error); + } + }; + + if text.is_empty() { + out.reserve_exact(depth + 1); + unsafe { out.as_mut_ptr().add(depth).write(comparator) } + return Ok(depth + 1); + } + + let text = if let Some(text) = text.strip_prefix(',') { + text.trim_start_matches(' ') + } else { + let unexpected = text.chars().next().unwrap(); + return Err(Error::new(ErrorKind::ExpectedCommaFound(pos, unexpected))); + }; + + const MAX_COMPARATORS: usize = 32; + if depth + 1 == MAX_COMPARATORS { + return Err(Error::new(ErrorKind::ExcessiveComparators)); + } + + // Recurse to collect parsed Comparator objects on the stack. We perform a + // single allocation to allocate exactly the right sized Vec only once the + // total number of comparators is known. + let len = version_req(text, out, depth + 1)?; + unsafe { out.as_mut_ptr().add(depth).write(comparator) } + Ok(len) +} |