r_description/
relations.rs1use crate::version::Version;
3use std::borrow::Cow;
4use std::iter::Peekable;
5use std::str::Chars;
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
9pub enum VersionConstraint {
10 LessThan, LessThanEqual, Equal, GreaterThan, GreaterThanEqual, }
21
22impl std::str::FromStr for VersionConstraint {
23 type Err = String;
24
25 fn from_str(s: &str) -> Result<Self, Self::Err> {
26 match s {
27 ">=" => Ok(VersionConstraint::GreaterThanEqual),
28 "<=" => Ok(VersionConstraint::LessThanEqual),
29 "=" => Ok(VersionConstraint::Equal),
30 ">>" => Ok(VersionConstraint::GreaterThan),
31 "<<" => Ok(VersionConstraint::LessThan),
32 _ => Err(format!("Invalid version constraint: {s}")),
33 }
34 }
35}
36
37impl std::fmt::Display for VersionConstraint {
38 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
39 match self {
40 VersionConstraint::GreaterThanEqual => f.write_str(">="),
41 VersionConstraint::LessThanEqual => f.write_str("<="),
42 VersionConstraint::Equal => f.write_str("="),
43 VersionConstraint::GreaterThan => f.write_str(">>"),
44 VersionConstraint::LessThan => f.write_str("<<"),
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
52#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
53#[repr(u16)]
54#[allow(missing_docs)]
55pub(crate) enum SyntaxKind {
56 IDENT = 0, COMMA, L_PARENS, R_PARENS, L_ANGLE, R_ANGLE, EQUAL, WHITESPACE, NEWLINE, ERROR, ROOT, RELATION, VERSION, CONSTRAINT, }
73
74impl From<SyntaxKind> for rowan::SyntaxKind {
76 fn from(kind: SyntaxKind) -> Self {
77 Self(kind as u16)
78 }
79}
80
81pub(crate) struct Lexer<'a> {
83 input: Peekable<Chars<'a>>,
84}
85
86impl<'a> Lexer<'a> {
87 pub fn new(input: &'a str) -> Self {
89 Lexer {
90 input: input.chars().peekable(),
91 }
92 }
93
94 fn is_whitespace(c: char) -> bool {
95 c == ' ' || c == '\t' || c == '\r'
96 }
97
98 fn is_valid_ident_char(c: char) -> bool {
99 c.is_ascii_alphanumeric() || c == '-' || c == '.'
100 }
101
102 fn read_while<F>(&mut self, predicate: F) -> String
103 where
104 F: Fn(char) -> bool,
105 {
106 let mut result = String::new();
107 while let Some(&c) = self.input.peek() {
108 if predicate(c) {
109 result.push(c);
110 self.input.next();
111 } else {
112 break;
113 }
114 }
115 result
116 }
117
118 fn next_token(&mut self) -> Option<(SyntaxKind, String)> {
119 if let Some(&c) = self.input.peek() {
120 match c {
121 ',' => {
122 self.input.next();
123 Some((SyntaxKind::COMMA, ",".to_owned()))
124 }
125 '(' => {
126 self.input.next();
127 Some((SyntaxKind::L_PARENS, "(".to_owned()))
128 }
129 ')' => {
130 self.input.next();
131 Some((SyntaxKind::R_PARENS, ")".to_owned()))
132 }
133 '<' => {
134 self.input.next();
135 Some((SyntaxKind::L_ANGLE, "<".to_owned()))
136 }
137 '>' => {
138 self.input.next();
139 Some((SyntaxKind::R_ANGLE, ">".to_owned()))
140 }
141 '=' => {
142 self.input.next();
143 Some((SyntaxKind::EQUAL, "=".to_owned()))
144 }
145 '\n' => {
146 self.input.next();
147 Some((SyntaxKind::NEWLINE, "\n".to_owned()))
148 }
149 _ if Self::is_whitespace(c) => {
150 let whitespace = self.read_while(Self::is_whitespace);
151 Some((SyntaxKind::WHITESPACE, whitespace))
152 }
153 _ if Self::is_valid_ident_char(c) => {
155 let key = self.read_while(Self::is_valid_ident_char);
156 Some((SyntaxKind::IDENT, key))
157 }
158 _ => {
159 self.input.next();
160 Some((SyntaxKind::ERROR, c.to_string()))
161 }
162 }
163 } else {
164 None
165 }
166 }
167}
168
169impl Iterator for Lexer<'_> {
170 type Item = (SyntaxKind, String);
171
172 fn next(&mut self) -> Option<Self::Item> {
173 self.next_token()
174 }
175}
176
177pub(crate) fn lex(input: &str) -> Vec<(SyntaxKind, String)> {
178 let mut lexer = Lexer::new(input);
179 lexer.by_ref().collect::<Vec<_>>()
180}
181
182pub trait VersionLookup {
184 fn lookup_version<'a>(&'a self, package: &'_ str) -> Option<std::borrow::Cow<'a, Version>>;
186}
187
188impl VersionLookup for std::collections::HashMap<String, Version> {
189 fn lookup_version<'a>(&'a self, package: &str) -> Option<Cow<'a, Version>> {
190 self.get(package).map(Cow::Borrowed)
191 }
192}
193
194impl<F> VersionLookup for F
195where
196 F: Fn(&str) -> Option<Version>,
197{
198 fn lookup_version<'a>(&'a self, name: &str) -> Option<Cow<'a, Version>> {
199 self(name).map(Cow::Owned)
200 }
201}
202
203impl VersionLookup for (String, Version) {
204 fn lookup_version<'a>(&'a self, name: &str) -> Option<Cow<'a, Version>> {
205 if name == self.0 {
206 Some(Cow::Borrowed(&self.1))
207 } else {
208 None
209 }
210 }
211}