70fbc00aa1173c3c5852531c2d6161f1f4580735
[pspp] / rust / src / identifier.rs
1 use std::{
2     borrow::Borrow,
3     cmp::Ordering,
4     fmt::{Debug, Display, Formatter, Result as FmtResult},
5     hash::{Hash, Hasher},
6 };
7
8 use encoding_rs::{EncoderResult, Encoding, UTF_8};
9 use finl_unicode::categories::{CharacterCategories, MajorCategory};
10 use thiserror::Error as ThisError;
11 use unicase::UniCase;
12
13 pub trait IdentifierChar {
14     /// Returns true if `self` may be the first character in an identifier.
15     fn may_start_id(self) -> bool;
16
17     /// Returns true if `self` may be a second or subsequent character in an
18     /// identifier.
19     fn may_continue_id(self) -> bool;
20 }
21
22 impl IdentifierChar for char {
23     fn may_start_id(self) -> bool {
24         use MajorCategory::*;
25
26         ([L, M, S].contains(&self.get_major_category()) || "@#$".contains(self))
27             && self != char::REPLACEMENT_CHARACTER
28     }
29
30     fn may_continue_id(self) -> bool {
31         use MajorCategory::*;
32
33         ([L, M, S, N].contains(&self.get_major_category()) || "@#$._".contains(self))
34             && self != char::REPLACEMENT_CHARACTER
35     }
36 }
37
38 #[derive(Clone, Debug, ThisError)]
39 pub enum Error {
40     #[error("Identifier cannot be empty string.")]
41     Empty,
42
43     #[error("\"{0}\" may not be used as an identifier because it is a reserved word.")]
44     Reserved(String),
45
46     #[error("\"{0}\" may not be used as an identifier because it begins with disallowed character \"{1}\".")]
47     BadFirstCharacter(String, char),
48
49     #[error("\"{0}\" may not be used as an identifier because it contains disallowed character \"{1}\".")]
50     BadLaterCharacter(String, char),
51
52     #[error("Identifier \"{id}\" is {length} bytes in the encoding in use ({encoding}), which exceeds the {max}-byte limit.")]
53     TooLong {
54         id: String,
55         length: usize,
56         encoding: &'static str,
57         max: usize,
58     },
59
60     #[error("\"{id}\" may not be used as an identifier because the encoding in use ({encoding}) cannot represent \"{c}\".")]
61     NotEncodable {
62         id: String,
63         encoding: &'static str,
64         c: char,
65     },
66 }
67
68 fn is_reserved_word(s: &str) -> bool {
69     for word in [
70         "and", "or", "not", "eq", "ge", "gt", "le", "ne", "all", "by", "to", "with",
71     ] {
72         if s.eq_ignore_ascii_case(word) {
73             return true;
74         }
75     }
76     false
77 }
78
79 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
80 pub struct Identifier(pub UniCase<String>);
81
82 impl Identifier {
83     /// Maximum length of an identifier, in bytes.  The limit applies in the
84     /// encoding used by the dictionary, not in UTF-8.
85     pub const MAX_LEN: usize = 64;
86
87     pub fn new_utf8(s: &str) -> Result<Identifier, Error> {
88         Self::new(s, UTF_8)
89     }
90     pub fn new(s: &str, encoding: &'static Encoding) -> Result<Identifier, Error> {
91         Self::is_plausible(s)?;
92         let identifier = Identifier(s.into());
93         identifier.check_encoding(encoding)?;
94         Ok(identifier)
95     }
96     /// Checks whether this is a valid identifier in the given `encoding`.  An
97     /// identifier that is valid in one encoding might be invalid in another
98     /// because some characters are unencodable or because it is too long.
99     pub fn check_encoding(&self, encoding: &'static Encoding) -> Result<(), Error> {
100         let s = self.0.as_str();
101         let (encoded, _, unencodable) = encoding.encode(s);
102         if unencodable {
103             let mut encoder = encoding.new_encoder();
104             let mut buf = Vec::with_capacity(
105                 encoder
106                     .max_buffer_length_from_utf8_without_replacement(s.len())
107                     .unwrap(),
108             );
109             let EncoderResult::Unmappable(c) = encoder
110                 .encode_from_utf8_to_vec_without_replacement(s, &mut buf, true)
111                 .0
112             else {
113                 unreachable!();
114             };
115             return Err(Error::NotEncodable {
116                 id: s.into(),
117                 encoding: encoding.name(),
118                 c,
119             });
120         }
121         if encoded.len() > Self::MAX_LEN {
122             return Err(Error::TooLong {
123                 id: s.into(),
124                 length: encoded.len(),
125                 encoding: encoding.name(),
126                 max: Self::MAX_LEN,
127             });
128         }
129         Ok(())
130     }
131     pub fn is_plausible(s: &str) -> Result<(), Error> {
132         if s.is_empty() {
133             return Err(Error::Empty);
134         }
135         if is_reserved_word(s) {
136             return Err(Error::Reserved(s.into()));
137         }
138
139         let mut i = s.chars();
140         let first = i.next().unwrap();
141         if !first.may_start_id() {
142             return Err(Error::BadFirstCharacter(s.into(), first));
143         }
144         for c in i {
145             if !c.may_continue_id() {
146                 return Err(Error::BadLaterCharacter(s.into(), c));
147             }
148         }
149         Ok(())
150     }
151 }
152
153 impl Display for Identifier {
154     fn fmt(&self, f: &mut Formatter) -> FmtResult {
155         write!(f, "{}", self.0)
156     }
157 }
158
159 pub trait HasIdentifier {
160     fn identifier(&self) -> &Identifier;
161 }
162
163 pub struct ByIdentifier<T>(pub T)
164 where
165     T: HasIdentifier;
166
167 impl<T> ByIdentifier<T>
168 where
169     T: HasIdentifier,
170 {
171     pub fn new(inner: T) -> Self {
172         Self(inner)
173     }
174 }
175
176 impl<T> PartialEq for ByIdentifier<T>
177 where
178     T: HasIdentifier,
179 {
180     fn eq(&self, other: &Self) -> bool {
181         self.0.identifier().eq(other.0.identifier())
182     }
183 }
184
185 impl<T> Eq for ByIdentifier<T> where T: HasIdentifier {}
186
187 impl<T> PartialOrd for ByIdentifier<T>
188 where
189     T: HasIdentifier,
190 {
191     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
192         Some(self.cmp(other))
193     }
194 }
195
196 impl<T> Ord for ByIdentifier<T>
197 where
198     T: HasIdentifier,
199 {
200     fn cmp(&self, other: &Self) -> Ordering {
201         self.0.identifier().cmp(other.0.identifier())
202     }
203 }
204
205 impl<T> Hash for ByIdentifier<T>
206 where
207     T: HasIdentifier,
208 {
209     fn hash<H: Hasher>(&self, state: &mut H) {
210         self.0.identifier().hash(state)
211     }
212 }
213
214 impl<T> Borrow<Identifier> for ByIdentifier<T>
215 where
216     T: HasIdentifier,
217 {
218     fn borrow(&self) -> &Identifier {
219         self.0.identifier()
220     }
221 }
222
223 impl<T> Debug for ByIdentifier<T>
224 where
225     T: HasIdentifier + Debug,
226 {
227     fn fmt(&self, f: &mut Formatter) -> FmtResult {
228         self.0.fmt(f)
229     }
230 }
231
232 impl<T> Clone for ByIdentifier<T>
233 where
234     T: HasIdentifier + Clone,
235 {
236     fn clone(&self) -> Self {
237         Self(self.0.clone())
238     }
239 }