Skip to main content

grift_util/
lib.rs

1//! Shared utilities for the Grift Scheme implementation.
2//!
3//! This crate provides common functions used by both the proc-macro crate
4//! (`grift_macros`) and the rest of the workspace, avoiding duplication
5//! of logic such as Scheme-name → Rust-name conversion.
6
7/// Convert a Scheme-style name to PascalCase for use as a Rust enum variant.
8///
9/// # Conversion rules
10///
11/// | Scheme character | Rust equivalent |
12/// |-----------------|-----------------|
13/// | `-` or `_`      | capitalize next |
14/// | `!`             | stripped        |
15/// | `?`             | `P` (predicate) |
16/// | `=`             | `Eq`            |
17/// | `>`             | `Gt`            |
18/// | `<`             | `Lt`            |
19/// | `+`             | `Plus`          |
20/// | `*`             | `Star`          |
21/// | `/`             | `Slash`         |
22///
23/// # Examples
24///
25/// ```
26/// use grift_util::to_pascal_case;
27///
28/// assert_eq!(to_pascal_case("map"), "Map");
29/// assert_eq!(to_pascal_case("set-car!"), "SetCar");
30/// assert_eq!(to_pascal_case("null?"), "NullP");
31/// assert_eq!(to_pascal_case("char->integer"), "CharGtInteger");
32/// assert_eq!(to_pascal_case("rat+"), "RatPlus");
33/// assert_eq!(to_pascal_case("rat*"), "RatStar");
34/// assert_eq!(to_pascal_case("rat/"), "RatSlash");
35/// ```
36pub fn to_pascal_case(name: &str) -> String {
37    let mut result = String::new();
38    let mut capitalize_next = true;
39
40    for c in name.chars() {
41        match c {
42            '-' | '_' => {
43                capitalize_next = true;
44            }
45            '!' => {
46                // Skip exclamation marks
47            }
48            '?' => {
49                // Replace with 'P' (predicate convention)
50                result.push('P');
51                capitalize_next = true;
52            }
53            '=' => {
54                // Replace with 'Eq' for equality operators
55                result.push_str("Eq");
56                capitalize_next = true;
57            }
58            '>' => {
59                // Replace with 'Gt' for greater-than
60                result.push_str("Gt");
61                capitalize_next = true;
62            }
63            '<' => {
64                // Replace with 'Lt' for less-than
65                result.push_str("Lt");
66                capitalize_next = true;
67            }
68            '+' => {
69                // Replace with 'Plus' for addition
70                result.push_str("Plus");
71                capitalize_next = true;
72            }
73            '*' => {
74                // Replace with 'Star' for multiplication
75                result.push_str("Star");
76                capitalize_next = true;
77            }
78            '/' => {
79                // Replace with 'Slash' for division
80                result.push_str("Slash");
81                capitalize_next = true;
82            }
83            _ => {
84                if capitalize_next {
85                    result.extend(c.to_uppercase());
86                    capitalize_next = false;
87                } else {
88                    result.push(c);
89                }
90            }
91        }
92    }
93
94    result
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_basic_names() {
103        assert_eq!(to_pascal_case("map"), "Map");
104        assert_eq!(to_pascal_case("car"), "Car");
105        assert_eq!(to_pascal_case("cdr"), "Cdr");
106        assert_eq!(to_pascal_case("cons"), "Cons");
107        assert_eq!(to_pascal_case("list"), "List");
108    }
109
110    #[test]
111    fn test_hyphenated_names() {
112        assert_eq!(to_pascal_case("set-car!"), "SetCar");
113        assert_eq!(to_pascal_case("set-cdr!"), "SetCdr");
114        assert_eq!(to_pascal_case("make-vector"), "MakeVector");
115        assert_eq!(to_pascal_case("vector-ref"), "VectorRef");
116        assert_eq!(to_pascal_case("exact-integer?"), "ExactIntegerP");
117    }
118
119    #[test]
120    fn test_predicates() {
121        assert_eq!(to_pascal_case("null?"), "NullP");
122        assert_eq!(to_pascal_case("pair?"), "PairP");
123        assert_eq!(to_pascal_case("number?"), "NumberP");
124        assert_eq!(to_pascal_case("zero?"), "ZeroP");
125        assert_eq!(to_pascal_case("eq?"), "EqP");
126    }
127
128    #[test]
129    fn test_operators() {
130        // Single-character operators:
131        // '+' becomes 'Plus', '*' becomes 'Star', '/' becomes 'Slash'
132        assert_eq!(to_pascal_case("+"), "Plus");
133        assert_eq!(to_pascal_case("*"), "Star");
134        assert_eq!(to_pascal_case("/"), "Slash");
135        assert_eq!(to_pascal_case("="), "Eq");
136        assert_eq!(to_pascal_case(">"), "Gt");
137        assert_eq!(to_pascal_case("<"), "Lt");
138        assert_eq!(to_pascal_case(">="), "GtEq");
139        assert_eq!(to_pascal_case("<="), "LtEq");
140    }
141
142    #[test]
143    fn test_conversion_arrows() {
144        assert_eq!(to_pascal_case("char->integer"), "CharGtInteger");
145        assert_eq!(to_pascal_case("integer->char"), "IntegerGtChar");
146        assert_eq!(to_pascal_case("vector->list"), "VectorGtList");
147        assert_eq!(to_pascal_case("list->vector"), "ListGtVector");
148    }
149
150    #[test]
151    fn test_exclamation_stripping() {
152        assert_eq!(to_pascal_case("vector-set!"), "VectorSet");
153        assert_eq!(to_pascal_case("vector-fill!"), "VectorFill");
154        assert_eq!(to_pascal_case("string-set!"), "StringSet");
155    }
156}