mod bytes;
mod from_bits;
mod from_field;
mod parse;
mod random;
mod serialize;
mod to_bits;
mod to_field;
mod to_fields;
use crate::{Boolean, Field};
use snarkvm_console_network_environment::prelude::*;
pub const SIZE_IN_BYTES: usize = Field::<Console>::SIZE_IN_DATA_BITS / 8;
pub const SIZE_IN_BITS: usize = SIZE_IN_BYTES * 8;
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct IdentifierLiteral<E: Environment> {
bytes: [u8; SIZE_IN_BYTES],
_phantom: core::marker::PhantomData<E>,
}
impl<E: Environment> IdentifierLiteral<E> {
pub fn new(string: &str) -> Result<Self> {
ensure!(!string.is_empty(), "Identifier literal cannot be empty");
ensure!(string.len() <= SIZE_IN_BYTES, "Identifier literal exceeds {SIZE_IN_BYTES} bytes");
let mut bytes = [0u8; SIZE_IN_BYTES];
bytes[..string.len()].copy_from_slice(string.as_bytes());
validate_identifier_bytes(&bytes)?;
Ok(Self { bytes, _phantom: core::marker::PhantomData })
}
pub fn from_bytes_array(bytes: [u8; SIZE_IN_BYTES]) -> Result<Self> {
validate_identifier_bytes(&bytes)?;
Ok(Self { bytes, _phantom: core::marker::PhantomData })
}
pub fn bytes(&self) -> &[u8; SIZE_IN_BYTES] {
&self.bytes
}
pub fn length(&self) -> u8 {
#[allow(clippy::cast_possible_truncation)]
let length = self.bytes.iter().position(|&b| b == 0).unwrap_or(SIZE_IN_BYTES) as u8;
length
}
}
impl<E: Environment> TypeName for IdentifierLiteral<E> {
#[inline]
fn type_name() -> &'static str {
"identifier"
}
}
impl<E: Environment> Equal for IdentifierLiteral<E> {
type Output = Boolean<E>;
fn is_equal(&self, other: &Self) -> Self::Output {
Boolean::new(self == other)
}
fn is_not_equal(&self, other: &Self) -> Self::Output {
Boolean::new(self != other)
}
}
impl<E: Environment> SizeInBits for IdentifierLiteral<E> {
fn size_in_bits() -> usize {
SIZE_IN_BITS
}
}
impl<E: Environment> SizeInBytes for IdentifierLiteral<E> {
fn size_in_bytes() -> usize {
SIZE_IN_BYTES
}
}
fn validate_identifier_bytes(bytes: &[u8; SIZE_IN_BYTES]) -> Result<u8> {
let num_bytes = bytes.iter().position(|&b| b == 0).unwrap_or(SIZE_IN_BYTES);
ensure!(num_bytes > 0, "Identifier literal cannot be empty");
ensure!(bytes[num_bytes..].iter().all(|&b| b == 0), "Non-zero byte after null terminator");
ensure!(bytes[0].is_ascii_alphabetic(), "Identifier literal must start with a letter");
ensure!(
bytes[..num_bytes].iter().all(|b| b.is_ascii_alphanumeric() || *b == b'_'),
"Identifier literal must contain only letters, digits, and underscores"
);
#[allow(clippy::cast_possible_truncation)]
Ok(num_bytes as u8)
}
#[cfg(test)]
mod tests {
use super::*;
use snarkvm_console_network_environment::Console;
type CurrentEnvironment = Console;
#[test]
fn test_new_valid() {
assert!(IdentifierLiteral::<CurrentEnvironment>::new("a").is_ok());
assert!(IdentifierLiteral::<CurrentEnvironment>::new("hello").is_ok());
assert!(IdentifierLiteral::<CurrentEnvironment>::new("hello_world_42").is_ok());
let max_str = "a".repeat(SIZE_IN_BYTES);
assert!(IdentifierLiteral::<CurrentEnvironment>::new(&max_str).is_ok());
}
#[test]
fn test_new_empty_fails() {
assert!(IdentifierLiteral::<CurrentEnvironment>::new("").is_err());
}
#[test]
fn test_new_too_long_fails() {
let long_str = "a".repeat(SIZE_IN_BYTES + 1);
assert!(IdentifierLiteral::<CurrentEnvironment>::new(&long_str).is_err());
}
#[test]
fn test_new_must_start_with_letter() {
assert!(IdentifierLiteral::<CurrentEnvironment>::new("1abc").is_err());
assert!(IdentifierLiteral::<CurrentEnvironment>::new("_abc").is_err());
}
#[test]
fn test_new_invalid_chars_fails() {
assert!(IdentifierLiteral::<CurrentEnvironment>::new("hello world").is_err());
assert!(IdentifierLiteral::<CurrentEnvironment>::new("foo@bar").is_err());
assert!(IdentifierLiteral::<CurrentEnvironment>::new("foo-bar").is_err());
}
#[test]
fn test_equality() {
let a = IdentifierLiteral::<CurrentEnvironment>::new("hello").unwrap();
let b = IdentifierLiteral::<CurrentEnvironment>::new("hello").unwrap();
let c = IdentifierLiteral::<CurrentEnvironment>::new("world").unwrap();
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn test_size_in_bits() {
assert_eq!(IdentifierLiteral::<CurrentEnvironment>::size_in_bits(), 248);
}
#[test]
fn test_size_in_bytes() {
assert_eq!(IdentifierLiteral::<CurrentEnvironment>::size_in_bytes(), 31);
}
#[test]
fn test_validate_identifier_bytes_all_ascii() {
for byte in 0u8..=255 {
let mut bytes = [0u8; SIZE_IN_BYTES];
bytes[0] = byte;
let result = IdentifierLiteral::<CurrentEnvironment>::from_bytes_array(bytes);
let expected_valid = byte.is_ascii_alphabetic();
if expected_valid {
assert!(
result.is_ok(),
"First byte {byte} ('{}'): expected valid but got error",
if byte.is_ascii_graphic() { byte as char } else { '?' }
);
} else {
assert!(
result.is_err(),
"First byte {byte} ('{}'): expected error but got success",
if byte.is_ascii_graphic() { byte as char } else { '?' }
);
}
}
for byte in 0u8..=255 {
let mut bytes = [0u8; SIZE_IN_BYTES];
bytes[0] = b'a'; bytes[1] = byte;
let result = IdentifierLiteral::<CurrentEnvironment>::from_bytes_array(bytes);
let expected_valid = byte.is_ascii_alphanumeric() || byte == b'_' || byte == 0;
if expected_valid {
assert!(
result.is_ok(),
"Subsequent byte {byte} ('{}'): expected valid but got error",
if byte.is_ascii_graphic() { byte as char } else { '?' }
);
} else {
assert!(
result.is_err(),
"Subsequent byte {byte} ('{}'): expected error but got success",
if byte.is_ascii_graphic() { byte as char } else { '?' }
);
}
}
}
}