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