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 }