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