Skip to main content

use_go_value/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// Error returned when parsing Go value vocabulary fails.
8#[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/// Primitive Go-like value metadata.
26#[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    /// Returns a Go-like primitive type label.
39    #[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    /// Returns whether this value is nil.
53    #[must_use]
54    pub const fn is_nil(&self) -> bool {
55        matches!(self, Self::Nil)
56    }
57
58    /// Returns whether this value is numeric metadata.
59    #[must_use]
60    pub const fn is_numeric(&self) -> bool {
61        matches!(self, Self::Int(_) | Self::Float(_) | Self::Complex { .. })
62    }
63
64    /// Returns whether this value is zero-like for metadata purposes.
65    #[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/// Primitive numeric Go-like value metadata.
80#[derive(Clone, Debug, PartialEq)]
81pub enum GoNumericValue {
82    Int(String),
83    Float(f64),
84    Complex { real: f64, imag: f64 },
85}
86
87impl GoNumericValue {
88    /// Returns a Go-like numeric type label.
89    #[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    /// Converts the numeric metadata into a primitive value.
99    #[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/// Go string literal kind metadata.
110#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
111pub enum GoStringLiteralKind {
112    Interpreted,
113    Raw,
114}
115
116impl GoStringLiteralKind {
117    /// Returns the literal kind label.
118    #[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/// Go rune literal metadata.
146#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
147pub struct GoRuneLiteral(char);
148
149impl GoRuneLiteral {
150    /// Creates rune literal metadata from a character.
151    #[must_use]
152    pub const fn new(value: char) -> Self {
153        Self(value)
154    }
155
156    /// Returns the stored character.
157    #[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/// Go bool literal metadata.
170#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
171pub struct GoBoolLiteral(bool);
172
173impl GoBoolLiteral {
174    /// Creates bool literal metadata.
175    #[must_use]
176    pub const fn new(value: bool) -> Self {
177        Self(value)
178    }
179
180    /// Returns the stored bool.
181    #[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/// Go nil metadata.
194#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
195pub struct GoNil;
196
197impl GoNil {
198    /// Returns the nil label.
199    #[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}