Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / hexpm / src / version / parser.rs
12 kB 412 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2026 The Gleam contributors 3 4// Based off of https://github.com/steveklabnik/semver-parser/blob/bee9de80aaa9653c5eb46a83658606cb21151e65/src/parser.rs 5 6use std::fmt; 7use std::mem; 8 9use self::Error::*; 10use super::lexer::{self, Lexer, Token}; 11use crate::version::{Identifier, Version}; 12use thiserror::Error; 13 14type PubgrubRange = pubgrub::Range<Version>; 15 16#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Error)] 17pub enum Error { 18 /// Needed more tokens for parsing, but none are available. 19 UnexpectedEnd, 20 /// Unexpected token. 21 UnexpectedToken(String), 22 /// An error occurred in the lexer. 23 Lexer(lexer::Error), 24 /// More input available. 25 MoreInput(String), 26 /// Encountered empty predicate in a set of predicates. 27 EmptyPredicate, 28 /// Encountered an empty range. 29 EmptyRange, 30 /// Encountered a semver that's missing the minor and patch version. 31 MinorVersionMissing(u32), 32 /// Encountered a semver that's missing the patch version. 33 PatchVersionMissing(u32, u32), 34} 35 36impl From<lexer::Error> for Error { 37 fn from(value: lexer::Error) -> Self { 38 Error::Lexer(value) 39 } 40} 41 42impl fmt::Display for Error { 43 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 44 use self::Error::*; 45 46 match *self { 47 UnexpectedEnd => write!(fmt, "expected more input"), 48 UnexpectedToken(ref token) => write!(fmt, "encountered unexpected token: {token:?}"), 49 Lexer(ref error) => write!(fmt, "lexer error: {error:?}"), 50 MoreInput(ref tokens) => write!(fmt, "expected end of input, but got: {tokens:?}"), 51 EmptyPredicate => write!(fmt, "encountered empty predicate"), 52 EmptyRange => write!(fmt, "encountered empty range"), 53 MinorVersionMissing(major) => { 54 write!(fmt, "missing minor and patch versions: {major:?}") 55 } 56 PatchVersionMissing(major, minor) => { 57 write!(fmt, "missing patch version: {major:?}.{minor:?}") 58 } 59 } 60 } 61} 62 63/// impl for backwards compatibility. 64impl From<Error> for String { 65 fn from(value: Error) -> Self { 66 value.to_string() 67 } 68} 69 70/// A recursive-descent parser for parsing version requirements. 71pub struct Parser<'input> { 72 /// Source of token. 73 lexer: Lexer<'input>, 74 /// Lookaehead. 75 c1: Option<Token<'input>>, 76} 77 78impl<'input> Parser<'input> { 79 /// Construct a new parser for the given input. 80 pub fn new(input: &'input str) -> Result<Parser<'input>, Error> { 81 let mut lexer = Lexer::new(input); 82 83 let c1 = if let Some(c1) = lexer.next() { 84 Some(c1?) 85 } else { 86 None 87 }; 88 89 Ok(Parser { lexer, c1 }) 90 } 91 92 /// Pop one token. 93 #[inline(always)] 94 fn pop(&mut self) -> Result<Token<'input>, Error> { 95 let c1 = if let Some(c1) = self.lexer.next() { 96 Some(c1?) 97 } else { 98 None 99 }; 100 101 mem::replace(&mut self.c1, c1).ok_or(UnexpectedEnd) 102 } 103 104 /// Peek one token. 105 #[inline(always)] 106 fn peek(&mut self) -> Option<&Token<'input>> { 107 self.c1.as_ref() 108 } 109 110 /// Skip whitespace if present. 111 fn skip_whitespace(&mut self) -> Result<(), Error> { 112 match self.peek() { 113 Some(&Token::Whitespace(_, _)) => self.pop().map(|_| ()), 114 _ => Ok(()), 115 } 116 } 117 118 /// Check that some whitespace is next and then discard it 119 fn expect_whitespace(&mut self) -> Result<(), Error> { 120 match self.pop()? { 121 Token::Whitespace(_, _) => Ok(()), 122 token => Err(UnexpectedToken(token.to_string())), 123 } 124 } 125 126 /// Parse a single numeric. 127 pub fn numeric(&mut self) -> Result<u32, Error> { 128 match self.pop()? { 129 Token::Numeric(number) => Ok(number), 130 token => Err(UnexpectedToken(token.to_string())), 131 } 132 } 133 134 fn dot(&mut self) -> Result<(), Error> { 135 match self.pop()? { 136 Token::Dot => Ok(()), 137 token => Err(UnexpectedToken(token.to_string())), 138 } 139 } 140 141 /// Parse a dot, then a numeric. 142 fn dot_numeric(&mut self) -> Result<u32, Error> { 143 self.dot()?; 144 self.numeric() 145 } 146 147 /// Parse an string identifier. 148 /// 149 /// Like, `foo`, or `bar`, or `beta-1`. 150 pub fn identifier(&mut self) -> Result<Identifier, Error> { 151 let identifier = match self.pop()? { 152 Token::AlphaNumeric(identifier) => { 153 // TODO: Borrow? 154 Identifier::AlphaNumeric(identifier.to_string()) 155 } 156 Token::Numeric(n) => Identifier::Numeric(n), 157 tok => return Err(UnexpectedToken(tok.to_string())), 158 }; 159 160 if let Some(&Token::Hyphen) = self.peek() { 161 // pop the peeked hyphen 162 self.pop()?; 163 // concat with any following identifiers 164 Ok(identifier 165 .concat("-") 166 .concat(&self.identifier()?.to_string())) 167 } else { 168 Ok(identifier) 169 } 170 } 171 172 /// Parse all pre-release identifiers, separated by dots. 173 /// 174 /// Like, `abcdef.1234`. 175 fn pre(&mut self) -> Result<Vec<Identifier>, Error> { 176 match self.peek() { 177 Some(&Token::Hyphen) => {} 178 _ => return Ok(vec![]), 179 } 180 181 // pop the peeked hyphen. 182 self.pop()?; 183 self.parts() 184 } 185 186 /// Parse a dot-separated set of identifiers. 187 fn parts(&mut self) -> Result<Vec<Identifier>, Error> { 188 let mut parts = Vec::new(); 189 190 parts.push(self.identifier()?); 191 192 while let Some(&Token::Dot) = self.peek() { 193 self.pop()?; 194 195 parts.push(self.identifier()?); 196 } 197 198 Ok(parts) 199 } 200 201 /// Parse optional build metadata. 202 /// 203 /// Like, `` (empty), or `+abcdef`. 204 fn plus_build_metadata(&mut self) -> Result<Option<String>, Error> { 205 match self.peek() { 206 Some(&Token::Plus) => self.pop()?, 207 _ => return Ok(None), 208 }; 209 210 let mut buffer = String::new(); 211 212 loop { 213 match self.pop() { 214 Err(UnexpectedEnd) => break, 215 Ok(Token::LeadingZero(s)) => buffer.push_str(s), 216 Ok(Token::AlphaNumeric(s)) => buffer.push_str(s), 217 Ok(Token::Numeric(s)) => buffer.push_str(&s.to_string()), 218 Ok(Token::Dot) => buffer.push('.'), 219 Ok(token) => return Err(UnexpectedToken(token.to_string())), 220 Err(error) => return Err(error), 221 } 222 } 223 224 if buffer.is_empty() { 225 Err(UnexpectedEnd) 226 } else { 227 Ok(Some(buffer)) 228 } 229 } 230 231 /// Parse a version. 232 /// 233 /// Like, `1.0.0` or `3.0.0-beta.1`. 234 pub fn version(&mut self) -> Result<Version, Error> { 235 self.skip_whitespace()?; 236 237 let major = self.numeric()?; 238 let minor = self 239 .dot_numeric() 240 .map_err(|_| Error::MinorVersionMissing(major))?; 241 let patch = self 242 .dot_numeric() 243 .map_err(|_| Error::PatchVersionMissing(major, minor))?; 244 let pre = self.pre()?; 245 let build = self.plus_build_metadata()?; 246 247 self.skip_whitespace()?; 248 249 Ok(Version { 250 major, 251 minor, 252 patch, 253 pre, 254 build, 255 }) 256 } 257 258 /// Parse a version range requirement. 259 /// 260 /// Like, `~> 1.0.0` or `3.0.0-beta.1 or < 1.0 and > 0.2.3`. 261 pub fn range(&mut self) -> Result<PubgrubRange, Error> { 262 let mut range: Option<PubgrubRange> = None; 263 264 loop { 265 let constraint = self.range_ands_section()?; 266 range = Some(match range { 267 None => constraint, 268 Some(range) => range.union(&constraint), 269 }); 270 if self.peek() == Some(&Token::Or) { 271 self.pop()?; 272 self.expect_whitespace()?; 273 } else { 274 break; 275 } 276 } 277 278 self.skip_whitespace()?; 279 range.ok_or(UnexpectedEnd) 280 } 281 282 fn pessimistic_version_constraint(&mut self) -> Result<PubgrubRange, Error> { 283 let mut included_patch = false; 284 let major = self.numeric()?; 285 let minor = self.dot_numeric()?; 286 let patch = match self.peek() { 287 Some(Token::Dot) => { 288 included_patch = true; 289 self.pop()?; 290 self.numeric()? 291 } 292 _ => 0, 293 }; 294 let pre = self.pre()?; 295 let build = self.plus_build_metadata()?; 296 297 let lower = Version { 298 major, 299 minor, 300 patch, 301 pre, 302 build, 303 }; 304 let upper = if included_patch { 305 lower.bump_minor() 306 } else { 307 lower.bump_major() 308 }; 309 Ok( 310 PubgrubRange::higher_than(lower) 311 .intersection(&PubgrubRange::strictly_lower_than(upper)), 312 ) 313 } 314 315 fn range_ands_section(&mut self) -> Result<PubgrubRange, Error> { 316 use Token::*; 317 let mut range = None; 318 let and = |range: Option<PubgrubRange>, constraint: PubgrubRange| { 319 Some(match range { 320 None => constraint, 321 Some(range) => range.intersection(&constraint), 322 }) 323 }; 324 loop { 325 self.skip_whitespace()?; 326 match self.peek() { 327 None => break, 328 Some(Numeric(_)) => range = and(range, PubgrubRange::singleton(self.version()?)), 329 330 Some(Eq) => { 331 self.pop()?; 332 range = and(range, PubgrubRange::singleton(self.version()?)); 333 } 334 335 Some(NotEq) => { 336 self.pop()?; 337 let version = self.version()?; 338 let bumped = version.bump_patch(); 339 let below = PubgrubRange::strictly_lower_than(version); 340 let above = PubgrubRange::higher_than(bumped); 341 range = and(range, below.union(&above)); 342 } 343 344 Some(Gt) => { 345 self.pop()?; 346 range = and( 347 range, 348 PubgrubRange::higher_than(self.version()?.bump_patch()), 349 ); 350 } 351 352 Some(GtEq) => { 353 self.pop()?; 354 range = and(range, PubgrubRange::higher_than(self.version()?)); 355 } 356 357 Some(Lt) => { 358 self.pop()?; 359 range = and(range, PubgrubRange::strictly_lower_than(self.version()?)); 360 } 361 362 Some(LtEq) => { 363 self.pop()?; 364 range = and( 365 range, 366 PubgrubRange::strictly_lower_than(self.version()?.bump_patch()), 367 ); 368 } 369 370 Some(Pessimistic) => { 371 self.pop()?; 372 self.skip_whitespace()?; 373 range = and(range, self.pessimistic_version_constraint()?); 374 } 375 376 Some(_) => return Err(UnexpectedToken(self.pop()?.to_string())), 377 }; 378 379 self.skip_whitespace()?; 380 if self.peek() == Some(&Token::And) { 381 self.pop()?; 382 self.expect_whitespace()?; 383 } else { 384 break; 385 } 386 } 387 self.skip_whitespace()?; 388 range.ok_or(UnexpectedEnd) 389 } 390 391 /// Check if we have reached the end of input. 392 pub fn is_eof(&mut self) -> bool { 393 self.c1.is_none() 394 } 395 396 /// Get the rest of the tokens in the parser. 397 /// 398 /// Useful for debugging. 399 pub fn tail(&mut self) -> Result<Vec<Token<'input>>, Error> { 400 let mut out = Vec::new(); 401 402 if let Some(t) = self.c1.take() { 403 out.push(t); 404 } 405 406 for t in self.lexer.by_ref() { 407 out.push(t?); 408 } 409 410 Ok(out) 411 } 412}