1use std::collections::HashMap;
2use std::fmt::{Display, Formatter};
3use std::rc::Rc;
4
5use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
6use lazy_static::lazy_static;
7use thiserror::Error;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
28pub struct Name {
29 validated: Rc<String>,
30}
31
32impl From<Name> for String {
33 fn from(x: Name) -> Self {
34 x.validated.to_string()
35 }
36}
37
38impl std::ops::Deref for Name {
39 type Target = str;
40
41 fn deref(&self) -> &Self::Target {
42 self.validated.as_str()
43 }
44}
45
46impl Display for Name {
47 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
48 write!(f, "{}", self.validated.as_str())
49 }
50}
51
52impl PartialEq<&str> for Name {
53 fn eq(&self, other: &&str) -> bool {
54 &self.as_ref() == other
55 }
56}
57
58impl PartialEq<String> for Name {
59 fn eq(&self, other: &String) -> bool {
60 self.as_ref() == other
61 }
62}
63
64impl PartialEq<str> for Name {
65 fn eq(&self, other: &str) -> bool {
66 self.as_ref() == other
67 }
68}
69
70pub trait IntoName {
71 fn into_name(self) -> Result<Name, BadName>;
72}
73
74impl<T> IntoName for T
75where
76 T: AsRef<str>,
77{
78 fn into_name(self) -> Result<Name, BadName> {
79 Name::create(self.as_ref())
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct BadName {
85 pub(crate) name: String,
86 pub(crate) error: NameError,
87}
88
89impl BadName {
90 fn new(name: String, error: NameError) -> Self {
91 Self { name, error }
92 }
93}
94
95impl std::error::Error for BadName {}
96
97impl Display for BadName {
98 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99 write!(f, "name: {} error: {}", self.name, self.error)
100 }
101}
102
103#[non_exhaustive]
104#[derive(Error, Debug, Copy, Clone, PartialEq, Eq)]
105pub(crate) enum NameError {
106 #[error("Name is an empty string")]
107 IsEmpty,
108 #[error("Name contains invalid character '{}'", c)]
109 CharacterNeverAllowed { c: char },
110 #[error("Name must start with lowercase ascii but first character is '{}'", c)]
111 FirstCharacterNotLowercaseAscii { c: char },
112 #[error(
113 "'{}' is a reserved identifier in backend language '{}' ",
114 id,
115 language
116 )]
117 ReservedIdentifier {
118 id: &'static str,
119 language: &'static str,
120 },
121 #[error("'{}' is a sub-phrase reserved by oo-bindgen itself", phrase)]
122 BindgenConflict { phrase: &'static str },
123 #[error("Names cannot contain double underscores")]
124 ContainsDoubleUnderscore,
125 #[error("Names cannot end in underscores")]
126 LastCharacterIsUnderscore,
127}
128
129impl NameError {
130 fn character_never_allowed(c: char) -> NameError {
131 NameError::CharacterNeverAllowed { c }
132 }
133
134 fn first_character_not_lower_case_ascii(c: char) -> NameError {
135 NameError::FirstCharacterNotLowercaseAscii { c }
136 }
137
138 fn reserved_identifier(id: &'static str, language: &'static str) -> NameError {
139 NameError::ReservedIdentifier { id, language }
140 }
141}
142
143impl AsRef<str> for Name {
144 fn as_ref(&self) -> &str {
145 self.validated.as_str()
146 }
147}
148
149impl Name {
150 pub(crate) fn camel_case(&self) -> String {
152 self.validated.to_upper_camel_case()
153 }
154
155 pub(crate) fn capital_snake_case(&self) -> String {
157 self.validated.to_shouty_snake_case()
158 }
159
160 pub(crate) fn mixed_case(&self) -> String {
162 self.validated.to_lower_camel_case()
163 }
164
165 pub(crate) fn kebab_case(&self) -> String {
167 self.validated.to_kebab_case()
168 }
169
170 pub fn create<S: AsRef<str>>(value: S) -> Result<Self, BadName> {
172 Self::create_impl(value.as_ref())
173 }
174
175 #[must_use]
177 pub(crate) fn append(&self, other: &Name) -> Self {
178 Self {
179 validated: Rc::new(format!(
180 "{}_{}",
181 self.validated.as_str(),
182 other.validated.as_str()
183 )),
184 }
185 }
186
187 fn is_allowed(c: char) -> bool {
188 c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'
189 }
190
191 fn create_impl(value: &str) -> Result<Self, BadName> {
192 if let Some((keyword, lang)) = KEYWORD_MAP.get(value) {
193 return Err(BadName::new(
194 value.to_string(),
195 NameError::reserved_identifier(keyword, lang),
196 ));
197 }
198
199 match value.chars().next() {
200 Some(c) => {
201 if !c.is_ascii_lowercase() {
202 return Err(BadName::new(
203 value.to_string(),
204 NameError::first_character_not_lower_case_ascii(c),
205 ));
206 }
207 }
208 None => return Err(BadName::new(value.to_string(), NameError::IsEmpty)),
209 }
210
211 if let Some(bad_character) = value.chars().find(|c| !Self::is_allowed(*c)) {
212 return Err(BadName::new(
213 value.to_string(),
214 NameError::character_never_allowed(bad_character),
215 ));
216 }
217
218 if value.contains("__") {
219 return Err(BadName::new(
220 value.to_string(),
221 NameError::ContainsDoubleUnderscore,
222 ));
223 }
224
225 if let Some('_') = value.chars().last() {
226 return Err(BadName::new(
227 value.to_string(),
228 NameError::LastCharacterIsUnderscore,
229 ));
230 }
231
232 for phrase in OO_BINDGEN_RESERVED_PHRASES {
233 if value.contains(phrase) {
234 return Err(BadName::new(
235 value.to_string(),
236 NameError::BindgenConflict { phrase },
237 ));
238 }
239 }
240
241 Ok(Name {
242 validated: Rc::new(value.to_string()),
243 })
244 }
245}
246
247lazy_static! {
248 static ref KEYWORD_MAP: KeywordMap = KeywordMap::new();
249}
250
251const RUST_KEYWORDS: &[&str] = &[
252 "abstract", "as", "async", "await", "become", "box", "break", "const", "continue", "crate",
253 "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in",
254 "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub", "ref",
255 "return", "self", "Self", "static", "struct", "super", "trait", "true", "try", "type",
256 "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield",
257];
258
259const C_KEYWORDS: &[&str] = &[
260 "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else",
261 "enum", "extern", "float", "for", "goto", "if", "int", "long", "register", "return", "short",
262 "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void",
263 "volatile", "while",
264];
265
266const CPP_KEYWORDS: &[&str] = &[
267 "alignas",
268 "alignof",
269 "and",
270 "and_eq",
271 "asm",
272 "auto",
273 "bitand",
274 "bitor",
275 "bool",
276 "break",
277 "case",
278 "catch",
279 "char",
280 "char16_t",
281 "char32_t",
282 "char8_t",
283 "class",
284 "co_await",
285 "co_return",
286 "co_yield",
287 "compl",
288 "concept",
289 "const",
290 "const_cast",
291 "consteval",
292 "constexpr",
293 "constinit",
294 "continue",
295 "declaration",
296 "decltype",
297 "default",
298 "delete",
299 "directive",
300 "do",
301 "double",
302 "dynamic_cast",
303 "else",
304 "enum",
305 "explicit",
306 "export",
307 "extern",
308 "false",
309 "float",
310 "for",
311 "friend",
312 "goto",
313 "if",
314 "inline",
315 "int",
316 "long",
317 "mutable",
318 "namespace",
319 "new",
320 "noexcept",
321 "not",
322 "not_eq",
323 "nullptr",
324 "operator",
325 "or",
326 "or_eq",
327 "private",
328 "protected",
329 "public",
330 "register",
331 "reinterpret_cast",
332 "requires",
333 "return",
334 "short",
335 "signed",
336 "sizeof",
337 "static",
338 "static_assert",
339 "static_cast",
340 "struct",
341 "switch",
342 "template",
343 "this",
344 "thread_local",
345 "throw",
346 "true",
347 "try",
348 "typedef",
349 "typeid",
350 "typename",
351 "union",
352 "unsigned",
353 "using",
354 "using",
355 "virtual",
356 "void",
357 "volatile",
358 "wchar_t",
359 "while",
360 "xor",
361 "xor_eq",
362];
363
364const JAVA_KEYWORDS: &[&str] = &[
365 "abstract",
366 "assert",
367 "boolean",
368 "break",
369 "byte",
370 "case",
371 "catch",
372 "char",
373 "class",
374 "const",
375 "continue",
376 "default",
377 "do",
378 "double",
379 "else",
380 "enum",
381 "extends",
382 "final",
383 "finally",
384 "float",
385 "for",
386 "goto",
387 "if",
388 "implements",
389 "import",
390 "instanceof",
391 "int",
392 "interface",
393 "long",
394 "native",
395 "new",
396 "package",
397 "private",
398 "protected",
399 "public",
400 "return",
401 "short",
402 "static",
403 "strictfp",
404 "super",
405 "switch",
406 "synchronized",
407 "this",
408 "throw",
409 "throws",
410 "transient",
411 "try",
412 "void",
413 "volatile",
414 "while",
415];
416
417const CSHARP_KEYWORDS: &[&str] = &[
418 "abstract",
419 "as",
420 "base",
421 "bool",
422 "break",
423 "byte",
424 "case",
425 "catch",
426 "char",
427 "checked",
428 "class",
429 "const",
430 "continue",
431 "decimal",
432 "default",
433 "delegate",
434 "do",
435 "double",
436 "else",
437 "enum",
438 "event",
439 "explicit",
440 "extern",
441 "false",
442 "finally",
443 "fixed",
444 "float",
445 "for",
446 "foreach",
447 "goto",
448 "if",
449 "implicit",
450 "in",
451 "int",
452 "interface",
453 "internal",
454 "is",
455 "lock",
456 "long",
457 "namespace",
458 "new",
459 "null",
460 "object",
461 "operator",
462 "out",
463 "override",
464 "params",
465 "private",
466 "protected",
467 "public",
468 "readonly",
469 "ref",
470 "return",
471 "sbyte",
472 "sealed",
473 "short",
474 "sizeof",
475 "stackalloc",
476 "static",
477 "string",
478 "struct",
479 "switch",
480 "this",
481 "throw",
482 "true",
483 "try",
484 "typeof",
485 "uint",
486 "ulong",
487 "unchecked",
488 "unsafe",
489 "ushort",
490 "using",
491 "virtual",
492 "void",
493 "volatile",
494 "while",
495];
496
497const OO_BINDGEN_RESERVED_PHRASES: &[&str] = &[
503 "oo_bindgen",
504 "backend_library_loader",
506];
507
508type KeyWordMap = HashMap<&'static str, (&'static str, &'static str)>;
509
510struct KeywordMap {
511 map: KeyWordMap,
512}
513
514impl KeywordMap {
515 fn new() -> Self {
516 let mut map = KeyWordMap::new();
517
518 for x in RUST_KEYWORDS {
519 map.insert(x, (x, "Rust"));
520 }
521
522 for x in C_KEYWORDS {
523 map.insert(x, (x, "C"));
524 }
525
526 for x in CPP_KEYWORDS {
527 map.insert(x, (x, "C++"));
528 }
529
530 for x in JAVA_KEYWORDS {
531 map.insert(x, (x, "Java"));
532 }
533
534 for x in CSHARP_KEYWORDS {
535 map.insert(x, (x, "C#"));
536 }
537
538 Self { map }
539 }
540
541 fn get(&self, x: &str) -> Option<&(&'static str, &'static str)> {
542 self.map.get(x)
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549
550 #[test]
551 fn allows_valid_names() {
552 assert!(Name::create("a1").is_ok());
553 assert!(Name::create("abc_def").is_ok());
554 assert!(Name::create("a1_d_2").is_ok());
555 }
556
557 #[test]
558 fn cannot_equal_reserved_identifiers() {
559 assert_eq!(
560 Name::create("alignas").err().unwrap().error,
561 NameError::reserved_identifier("alignas", "C++")
562 );
563 assert_eq!(
564 Name::create("implements").err().unwrap().error,
565 NameError::reserved_identifier("implements", "Java")
566 );
567 assert_eq!(
568 Name::create("delegate").err().unwrap().error,
569 NameError::reserved_identifier("delegate", "C#")
570 );
571 assert_eq!(
572 Name::create("box").err().unwrap().error,
573 NameError::reserved_identifier("box", "Rust")
574 );
575 }
576
577 #[test]
578 fn cannot_be_empty() {
579 assert_eq!(Name::create("").err().unwrap().error, NameError::IsEmpty);
580 }
581
582 #[test]
583 fn cannot_contain_uppercase() {
584 assert_eq!(
585 Name::create("aBc").err().unwrap().error,
586 NameError::character_never_allowed('B')
587 );
588 }
589
590 #[test]
591 fn cannot_lead_with_underscores_or_numbers() {
592 assert_eq!(
593 Name::create("_abc").err().unwrap().error,
594 NameError::first_character_not_lower_case_ascii('_')
595 );
596 assert_eq!(
597 Name::create("1abc").err().unwrap().error,
598 NameError::first_character_not_lower_case_ascii('1')
599 );
600 }
601
602 #[test]
603 fn last_character_cannot_be_underscore() {
604 assert_eq!(
605 Name::create("abc_").err().unwrap().error,
606 NameError::LastCharacterIsUnderscore
607 );
608 }
609
610 #[test]
611 fn cannot_contain_double_underscore() {
612 assert_eq!(
613 Name::create("abc_def__ghi").err().unwrap().error,
614 NameError::ContainsDoubleUnderscore
615 );
616 }
617
618 #[test]
619 fn cannot_contain_a_sub_phrase_reserved_by_oo_bindgen() {
620 assert_eq!(
621 Name::create("blah_blah_oo_bindgen_blad")
622 .err()
623 .unwrap()
624 .error,
625 NameError::BindgenConflict {
626 phrase: "oo_bindgen"
627 }
628 );
629 }
630
631 #[test]
632 fn can_append_string() {
633 assert_eq!(
634 Name::create("abc")
635 .unwrap()
636 .append(&Name::create("def").unwrap())
637 .as_ref(),
638 "abc_def"
639 );
640 }
641
642 #[test]
643 fn names_are_compared_by_inner_value() {
644 assert_eq!(Name::create("abc"), Name::create("abc"));
645 }
646
647 #[test]
648 fn name_not_equal_works_as_expected() {
649 assert_ne!(Name::create("abc"), Name::create("def"));
650 }
651}