Skip to main content

snarkvm_console_program/data/identifier/
parse.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18impl<N: Network> Parser for Identifier<N> {
19    /// Parses a string into an identifier.
20    ///
21    /// # Requirements
22    /// The identifier must be alphanumeric (or underscore).
23    /// The identifier must not start with a number.
24    /// The identifier must not be a keyword.
25    #[inline]
26    fn parse(string: &str) -> ParserResult<Self> {
27        // Check for alphanumeric characters and underscores.
28        map_res(recognize(pair(alpha1, many0(alt((alphanumeric1, tag("_")))))), |identifier: &str| {
29            Self::from_str(identifier)
30        })(string)
31    }
32}
33
34impl<N: Network> FromStr for Identifier<N> {
35    type Err = Error;
36
37    /// Reads in an identifier from a string.
38    fn from_str(identifier: &str) -> Result<Self, Self::Err> {
39        // Ensure the identifier is not an empty string, and starts with an ASCII letter.
40        // Note. The first character must not be the NULL character for sound encoding of dynamic futures. See the implementation of `ToBits` for `Argument`.
41        match identifier.chars().next() {
42            Some(character) => ensure!(character.is_ascii_alphabetic(), "Identifier must start with a letter"),
43            None => bail!("Identifier cannot be empty"),
44        }
45
46        // Ensure the identifier consists of ASCII letters, ASCII digits, and underscores.
47        if identifier.chars().any(|character| !character.is_ascii_alphanumeric() && character != '_') {
48            bail!("Identifier '{identifier}' must consist of letters, digits, and underscores")
49        }
50
51        // Ensure identifier fits within the data capacity of the base field.
52        let max_bytes = Field::<N>::size_in_data_bits() / 8; // Note: This intentionally rounds down.
53        if identifier.len() > max_bytes {
54            bail!("Identifier is too large. Identifiers must be <= {max_bytes} bytes long")
55        }
56
57        // Ensure that the identifier is not a literal.
58        ensure!(
59            !enum_iterator::all::<crate::LiteralType>().any(|lt| lt.type_name() == identifier),
60            "Identifier '{identifier}' is a reserved literal type"
61        );
62
63        // Note: The string bytes themselves are **not** little-endian. Rather, they are order-preserving
64        // for reconstructing the string when recovering the field element back into bytes.
65        Ok(Self(
66            Field::<N>::from_bits_le(&identifier.as_bytes().to_bits_le())?,
67            u8::try_from(identifier.len()).or_halt_with::<N>("Identifier `from_str` exceeds maximum length"),
68        ))
69    }
70}
71
72impl<N: Network> Debug for Identifier<N> {
73    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
74        Display::fmt(self, f)
75    }
76}
77
78impl<N: Network> Display for Identifier<N> {
79    /// Prints the identifier as a string.
80    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
81        // Convert the identifier to bytes.
82        let bytes = self.0.to_bytes_le().map_err(|_| fmt::Error)?;
83
84        // Parse the bytes as a UTF-8 string.
85        let string = String::from_utf8(bytes).map_err(|_| fmt::Error)?;
86
87        // Truncate the UTF-8 string at the first instance of '\0'.
88        match string.split('\0').next() {
89            // Check that the UTF-8 string matches the expected length.
90            Some(string) => match string.len() == self.1 as usize {
91                // Return the string.
92                true => write!(f, "{string}"),
93                false => Err(fmt::Error),
94            },
95            None => Err(fmt::Error),
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::data::identifier::tests::{sample_identifier, sample_identifier_as_string};
104    use snarkvm_console_network::MainnetV0;
105
106    type CurrentNetwork = MainnetV0;
107
108    const ITERATIONS: usize = 100;
109
110    #[test]
111    fn test_parse() -> Result<()> {
112        // Quick sanity check.
113        let (remainder, candidate) = Identifier::<CurrentNetwork>::parse("foo_bar1")?;
114        assert_eq!("foo_bar1", candidate.to_string());
115        assert_eq!("", remainder);
116
117        // Must be alphanumeric or underscore.
118        let (remainder, candidate) = Identifier::<CurrentNetwork>::parse("foo_bar~baz")?;
119        assert_eq!("foo_bar", candidate.to_string());
120        assert_eq!("~baz", remainder);
121
122        // Must be alphanumeric or underscore.
123        let (remainder, candidate) = Identifier::<CurrentNetwork>::parse("foo_bar-baz")?;
124        assert_eq!("foo_bar", candidate.to_string());
125        assert_eq!("-baz", remainder);
126
127        let mut rng = TestRng::default();
128
129        // Check random identifiers.
130        for _ in 0..ITERATIONS {
131            // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
132            let expected_string = sample_identifier_as_string::<CurrentNetwork>(&mut rng)?;
133            // Recover the field element from the bits.
134            let expected_field = Field::<CurrentNetwork>::from_bits_le(&expected_string.to_bits_le())?;
135
136            let (remainder, candidate) = Identifier::<CurrentNetwork>::parse(expected_string.as_str()).unwrap();
137            assert_eq!(expected_string, candidate.to_string());
138            assert_eq!(expected_field, candidate.0);
139            assert_eq!(expected_string.len(), candidate.1 as usize);
140            assert_eq!("", remainder);
141        }
142        Ok(())
143    }
144
145    #[test]
146    fn test_parse_fails() {
147        // Must not be solely underscores.
148        assert!(Identifier::<CurrentNetwork>::parse("_").is_err());
149        assert!(Identifier::<CurrentNetwork>::parse("__").is_err());
150        assert!(Identifier::<CurrentNetwork>::parse("___").is_err());
151        assert!(Identifier::<CurrentNetwork>::parse("____").is_err());
152
153        // Must not start with a number.
154        assert!(Identifier::<CurrentNetwork>::parse("1").is_err());
155        assert!(Identifier::<CurrentNetwork>::parse("2").is_err());
156        assert!(Identifier::<CurrentNetwork>::parse("3").is_err());
157        assert!(Identifier::<CurrentNetwork>::parse("1foo").is_err());
158        assert!(Identifier::<CurrentNetwork>::parse("12").is_err());
159        assert!(Identifier::<CurrentNetwork>::parse("111").is_err());
160
161        // Must fit within the data capacity of a base field element.
162        let identifier =
163            Identifier::<CurrentNetwork>::parse("foo_bar_baz_qux_quux_quuz_corge_grault_garply_waldo_fred_plugh_xyzzy");
164        assert!(identifier.is_err());
165    }
166
167    #[test]
168    fn test_from_str() -> Result<()> {
169        let candidate = Identifier::<CurrentNetwork>::from_str("foo_bar").unwrap();
170        assert_eq!("foo_bar", candidate.to_string());
171
172        let mut rng = TestRng::default();
173
174        for _ in 0..ITERATIONS {
175            // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
176            let expected_string = sample_identifier_as_string::<CurrentNetwork>(&mut rng)?;
177            // Recover the field element from the bits.
178            let expected_field = Field::<CurrentNetwork>::from_bits_le(&expected_string.to_bits_le())?;
179
180            let candidate = Identifier::<CurrentNetwork>::from_str(&expected_string)?;
181            assert_eq!(expected_string, candidate.to_string());
182            assert_eq!(expected_field, candidate.0);
183            assert_eq!(expected_string.len(), candidate.1 as usize);
184        }
185        Ok(())
186    }
187
188    #[test]
189    fn test_from_str_fails() {
190        // Must be non-empty.
191        assert!(Identifier::<CurrentNetwork>::from_str("").is_err());
192
193        // Must be alphanumeric or underscore.
194        assert!(Identifier::<CurrentNetwork>::from_str("foo_bar~baz").is_err());
195        assert!(Identifier::<CurrentNetwork>::from_str("foo_bar-baz").is_err());
196
197        // Must not be solely underscores.
198        assert!(Identifier::<CurrentNetwork>::from_str("_").is_err());
199        assert!(Identifier::<CurrentNetwork>::from_str("__").is_err());
200        assert!(Identifier::<CurrentNetwork>::from_str("___").is_err());
201        assert!(Identifier::<CurrentNetwork>::from_str("____").is_err());
202
203        // Must not start with a number.
204        assert!(Identifier::<CurrentNetwork>::from_str("1").is_err());
205        assert!(Identifier::<CurrentNetwork>::from_str("2").is_err());
206        assert!(Identifier::<CurrentNetwork>::from_str("3").is_err());
207        assert!(Identifier::<CurrentNetwork>::from_str("1foo").is_err());
208        assert!(Identifier::<CurrentNetwork>::from_str("12").is_err());
209        assert!(Identifier::<CurrentNetwork>::from_str("111").is_err());
210
211        // Must not start with underscore.
212        assert!(Identifier::<CurrentNetwork>::from_str("_foo").is_err());
213
214        // Must be ASCII.
215        assert!(Identifier::<CurrentNetwork>::from_str("\u{03b1}").is_err()); // Greek alpha
216        assert!(Identifier::<CurrentNetwork>::from_str("\u{03b2}").is_err()); // Greek beta
217
218        // Must fit within the data capacity of a base field element.
219        let identifier = Identifier::<CurrentNetwork>::from_str(
220            "foo_bar_baz_qux_quux_quuz_corge_grault_garply_waldo_fred_plugh_xyzzy",
221        );
222        assert!(identifier.is_err());
223    }
224
225    #[test]
226    fn test_display() -> Result<()> {
227        let identifier = Identifier::<CurrentNetwork>::from_str("foo_bar")?;
228        assert_eq!("foo_bar", format!("{identifier}"));
229        Ok(())
230    }
231
232    #[test]
233    fn test_proxy_bits_equivalence() -> Result<()> {
234        let mut rng = TestRng::default();
235        let identifier: Identifier<CurrentNetwork> = sample_identifier(&mut rng)?;
236
237        // Direct conversion to bytes.
238        let bytes1 = identifier.0.to_bytes_le()?;
239
240        // Combined conversion via bits.
241        let bits_le = identifier.0.to_bits_le();
242        let bytes2 = bits_le.chunks(8).map(u8::from_bits_le).collect::<Result<Vec<u8>, _>>()?;
243
244        assert_eq!(bytes1, bytes2);
245
246        Ok(())
247    }
248}