Fork of daniellemaywood.uk/gleam — Wasm codegen work
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}