summaryrefslogtreecommitdiffstats
path: root/third_party/rust/semver/src/parse.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/semver/src/parse.rs
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/semver/src/parse.rs')
-rw-r--r--third_party/rust/semver/src/parse.rs405
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)
+}