1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum GoValueParseError {
10 Empty,
11 Unknown,
12}
13
14impl fmt::Display for GoValueParseError {
15 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
16 match self {
17 Self::Empty => formatter.write_str("Go value label cannot be empty"),
18 Self::Unknown => formatter.write_str("unknown Go value label"),
19 }
20 }
21}
22
23impl Error for GoValueParseError {}
24
25#[derive(Clone, Debug, PartialEq)]
27pub enum GoPrimitiveValue {
28 Nil,
29 Bool(bool),
30 Int(String),
31 Float(f64),
32 Complex { real: f64, imag: f64 },
33 Rune(char),
34 String(String),
35}
36
37impl GoPrimitiveValue {
38 #[must_use]
40 pub const fn type_name(&self) -> &'static str {
41 match self {
42 Self::Nil => "nil",
43 Self::Bool(_) => "bool",
44 Self::Int(_) => "int",
45 Self::Float(_) => "float",
46 Self::Complex { .. } => "complex",
47 Self::Rune(_) => "rune",
48 Self::String(_) => "string",
49 }
50 }
51
52 #[must_use]
54 pub const fn is_nil(&self) -> bool {
55 matches!(self, Self::Nil)
56 }
57
58 #[must_use]
60 pub const fn is_numeric(&self) -> bool {
61 matches!(self, Self::Int(_) | Self::Float(_) | Self::Complex { .. })
62 }
63
64 #[must_use]
66 pub fn is_zero_like(&self) -> bool {
67 match self {
68 Self::Nil => true,
69 Self::Bool(value) => !value,
70 Self::Int(value) => is_zero_integer_text(value),
71 Self::Float(value) => *value == 0.0,
72 Self::Complex { real, imag } => *real == 0.0 && *imag == 0.0,
73 Self::Rune(value) => *value == '\0',
74 Self::String(value) => value.is_empty(),
75 }
76 }
77}
78
79#[derive(Clone, Debug, PartialEq)]
81pub enum GoNumericValue {
82 Int(String),
83 Float(f64),
84 Complex { real: f64, imag: f64 },
85}
86
87impl GoNumericValue {
88 #[must_use]
90 pub const fn type_name(&self) -> &'static str {
91 match self {
92 Self::Int(_) => "int",
93 Self::Float(_) => "float",
94 Self::Complex { .. } => "complex",
95 }
96 }
97
98 #[must_use]
100 pub fn into_primitive(self) -> GoPrimitiveValue {
101 match self {
102 Self::Int(value) => GoPrimitiveValue::Int(value),
103 Self::Float(value) => GoPrimitiveValue::Float(value),
104 Self::Complex { real, imag } => GoPrimitiveValue::Complex { real, imag },
105 }
106 }
107}
108
109#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
111pub enum GoStringLiteralKind {
112 Interpreted,
113 Raw,
114}
115
116impl GoStringLiteralKind {
117 #[must_use]
119 pub const fn as_str(self) -> &'static str {
120 match self {
121 Self::Interpreted => "interpreted",
122 Self::Raw => "raw",
123 }
124 }
125}
126
127impl fmt::Display for GoStringLiteralKind {
128 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
129 formatter.write_str(self.as_str())
130 }
131}
132
133impl FromStr for GoStringLiteralKind {
134 type Err = GoValueParseError;
135
136 fn from_str(input: &str) -> Result<Self, Self::Err> {
137 match normalized_label(input)?.as_str() {
138 "interpreted" | "quoted" => Ok(Self::Interpreted),
139 "raw" | "backtick" => Ok(Self::Raw),
140 _ => Err(GoValueParseError::Unknown),
141 }
142 }
143}
144
145#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
147pub struct GoRuneLiteral(char);
148
149impl GoRuneLiteral {
150 #[must_use]
152 pub const fn new(value: char) -> Self {
153 Self(value)
154 }
155
156 #[must_use]
158 pub const fn as_char(self) -> char {
159 self.0
160 }
161}
162
163impl fmt::Display for GoRuneLiteral {
164 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
165 write!(formatter, "{}", self.0)
166 }
167}
168
169#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
171pub struct GoBoolLiteral(bool);
172
173impl GoBoolLiteral {
174 #[must_use]
176 pub const fn new(value: bool) -> Self {
177 Self(value)
178 }
179
180 #[must_use]
182 pub const fn value(self) -> bool {
183 self.0
184 }
185}
186
187impl fmt::Display for GoBoolLiteral {
188 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
189 write!(formatter, "{}", self.0)
190 }
191}
192
193#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
195pub struct GoNil;
196
197impl GoNil {
198 #[must_use]
200 pub const fn as_str(self) -> &'static str {
201 "nil"
202 }
203}
204
205impl fmt::Display for GoNil {
206 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
207 formatter.write_str(self.as_str())
208 }
209}
210
211fn is_zero_integer_text(value: &str) -> bool {
212 let trimmed = value.trim();
213 let digits = trimmed
214 .strip_prefix(['+', '-'])
215 .unwrap_or(trimmed)
216 .trim_start_matches('0');
217 !trimmed.is_empty() && digits.is_empty()
218}
219
220fn normalized_label(input: &str) -> Result<String, GoValueParseError> {
221 let trimmed = input.trim();
222 if trimmed.is_empty() {
223 Err(GoValueParseError::Empty)
224 } else {
225 Ok(trimmed.to_ascii_lowercase())
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::{
232 GoBoolLiteral, GoNil, GoNumericValue, GoPrimitiveValue, GoRuneLiteral, GoStringLiteralKind,
233 GoValueParseError,
234 };
235
236 #[test]
237 fn reports_type_names() {
238 assert_eq!(GoPrimitiveValue::Nil.type_name(), "nil");
239 assert_eq!(GoPrimitiveValue::Bool(true).type_name(), "bool");
240 assert_eq!(GoPrimitiveValue::Int(String::from("42")).type_name(), "int");
241 assert_eq!(
242 GoPrimitiveValue::String(String::from("go")).type_name(),
243 "string"
244 );
245 }
246
247 #[test]
248 fn checks_zero_like_values() {
249 assert!(GoPrimitiveValue::Nil.is_zero_like());
250 assert!(GoPrimitiveValue::Bool(false).is_zero_like());
251 assert!(GoPrimitiveValue::Int(String::from("-0")).is_zero_like());
252 assert!(GoPrimitiveValue::Float(0.0).is_zero_like());
253 assert!(
254 GoPrimitiveValue::Complex {
255 real: 0.0,
256 imag: 0.0
257 }
258 .is_zero_like()
259 );
260 assert!(GoPrimitiveValue::Rune('\0').is_zero_like());
261 assert!(GoPrimitiveValue::String(String::new()).is_zero_like());
262 assert!(!GoPrimitiveValue::String(String::from("go")).is_zero_like());
263 }
264
265 #[test]
266 fn models_numeric_values() {
267 let numeric = GoNumericValue::Complex {
268 real: 1.0,
269 imag: 2.0,
270 };
271 assert_eq!(numeric.type_name(), "complex");
272 assert!(numeric.into_primitive().is_numeric());
273 }
274
275 #[test]
276 fn parses_string_literal_kinds() -> Result<(), GoValueParseError> {
277 assert_eq!(
278 "raw".parse::<GoStringLiteralKind>()?,
279 GoStringLiteralKind::Raw
280 );
281 assert_eq!(GoStringLiteralKind::Interpreted.to_string(), "interpreted");
282 assert_eq!(
283 "".parse::<GoStringLiteralKind>(),
284 Err(GoValueParseError::Empty)
285 );
286 Ok(())
287 }
288
289 #[test]
290 fn models_literal_wrappers() {
291 assert_eq!(GoRuneLiteral::new('g').as_char(), 'g');
292 assert!(GoBoolLiteral::new(true).value());
293 assert_eq!(GoNil.as_str(), "nil");
294 }
295}