use super::{CapturedAtom, LiteralReadCause};
pub fn capture_literal(spelling: &str) -> Result<CapturedAtom, LiteralReadCause> {
if opens_raw(spelling, "br") {
return Ok(CapturedAtom::ByteText(
raw_body(spelling, "br")?.as_bytes().to_vec(),
));
}
if opens_raw(spelling, "cr") {
return Ok(CapturedAtom::NulTerminatedText(
raw_body(spelling, "cr")?.as_bytes().to_vec(),
));
}
if opens_raw(spelling, "r") {
return Ok(CapturedAtom::Text(raw_body(spelling, "r")?.to_owned()));
}
if let Some(body) = quoted(spelling, "b") {
return byte_material(body).map(CapturedAtom::ByteText);
}
if let Some(body) = quoted(spelling, "c") {
return nul_terminated_material(body).map(CapturedAtom::NulTerminatedText);
}
if let Some(body) = quoted(spelling, "") {
return text_material(body).map(CapturedAtom::Text);
}
if let Some(body) = charred(spelling, "b") {
return one_byte(body).map(CapturedAtom::Byte);
}
if let Some(body) = charred(spelling, "") {
return one_character(body).map(CapturedAtom::Character);
}
if opens_number(spelling) {
return Ok(CapturedAtom::Number(spelling.to_owned()));
}
Err(LiteralReadCause::NotAKnownForm)
}
fn opens_raw(spelling: &str, opening: &str) -> bool {
spelling
.strip_prefix(opening)
.is_some_and(|rest| rest.starts_with('#') || rest.starts_with('"'))
}
fn quoted<'spelling>(spelling: &'spelling str, opening: &str) -> Option<&'spelling str> {
spelling
.strip_prefix(opening)?
.strip_prefix('"')?
.strip_suffix('"')
}
fn charred<'spelling>(spelling: &'spelling str, opening: &str) -> Option<&'spelling str> {
spelling
.strip_prefix(opening)?
.strip_prefix('\'')?
.strip_suffix('\'')
}
fn opens_number(spelling: &str) -> bool {
let unsigned = match spelling.strip_prefix('-') {
Some(unsigned) => unsigned,
None => spelling,
};
unsigned.starts_with(|character: char| character.is_ascii_digit())
}
fn raw_body<'spelling>(
spelling: &'spelling str,
opening: &str,
) -> Result<&'spelling str, LiteralReadCause> {
let rest = spelling
.strip_prefix(opening)
.ok_or(LiteralReadCause::NotReadable)?;
let hashes = rest
.chars()
.take_while(|character| *character == '#')
.count();
let mut body = rest
.get(hashes..)
.ok_or(LiteralReadCause::NotReadable)?
.strip_prefix('"')
.ok_or(LiteralReadCause::NotReadable)?;
for _ in 0..hashes {
body = body
.strip_suffix('#')
.ok_or(LiteralReadCause::NotReadable)?;
}
body.strip_suffix('"').ok_or(LiteralReadCause::NotReadable)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ReadUnit {
Character(char),
Byte(u8),
}
fn units(body: &str) -> Result<Vec<ReadUnit>, LiteralReadCause> {
let mut units = Vec::new();
let mut characters = body.chars().peekable();
while let Some(character) = characters.next() {
if character != '\\' {
units.push(ReadUnit::Character(character));
continue;
}
if let Some(unit) = escaped(&mut characters)? {
units.push(unit);
}
}
Ok(units)
}
fn escaped(
characters: &mut core::iter::Peekable<core::str::Chars<'_>>,
) -> Result<Option<ReadUnit>, LiteralReadCause> {
let marker = characters.next().ok_or(LiteralReadCause::NotReadable)?;
let unit = match marker {
'n' => ReadUnit::Character('\n'),
'r' => ReadUnit::Character('\r'),
't' => ReadUnit::Character('\t'),
'0' => ReadUnit::Character('\0'),
'\\' => ReadUnit::Character('\\'),
'\'' => ReadUnit::Character('\''),
'"' => ReadUnit::Character('"'),
'x' => ReadUnit::Byte(hexadecimal_byte(characters)?),
'u' => ReadUnit::Character(scalar_value(characters)?),
'\n' => {
while characters.next_if(|next| next.is_whitespace()).is_some() {}
return Ok(None);
}
_ => return Err(LiteralReadCause::NotReadable),
};
Ok(Some(unit))
}
fn hexadecimal_byte(
characters: &mut core::iter::Peekable<core::str::Chars<'_>>,
) -> Result<u8, LiteralReadCause> {
let high = hexadecimal_digit(characters.next())?;
let low = hexadecimal_digit(characters.next())?;
Ok((high << 4) | low)
}
fn hexadecimal_digit(character: Option<char>) -> Result<u8, LiteralReadCause> {
character
.and_then(|character| character.to_digit(16))
.and_then(|value| u8::try_from(value).ok())
.ok_or(LiteralReadCause::NotReadable)
}
fn scalar_value(
characters: &mut core::iter::Peekable<core::str::Chars<'_>>,
) -> Result<char, LiteralReadCause> {
if characters.next() != Some('{') {
return Err(LiteralReadCause::NotReadable);
}
let mut value: u32 = 0;
let mut read = false;
loop {
let character = characters.next().ok_or(LiteralReadCause::NotReadable)?;
if character == '}' {
break;
}
if character == '_' {
continue;
}
let digit = character
.to_digit(16)
.ok_or(LiteralReadCause::NotReadable)?;
value = value
.checked_mul(16)
.and_then(|shifted| shifted.checked_add(digit))
.ok_or(LiteralReadCause::NotReadable)?;
read = true;
}
if !read {
return Err(LiteralReadCause::NotReadable);
}
char::from_u32(value).ok_or(LiteralReadCause::NotReadable)
}
fn text_material(body: &str) -> Result<String, LiteralReadCause> {
let mut text = String::new();
for unit in units(body)? {
match unit {
ReadUnit::Character(character) => text.push(character),
ReadUnit::Byte(byte) => {
if !byte.is_ascii() {
return Err(LiteralReadCause::NotReadable);
}
text.push(char::from(byte));
}
}
}
Ok(text)
}
fn byte_material(body: &str) -> Result<Vec<u8>, LiteralReadCause> {
let mut material = Vec::new();
for unit in units(body)? {
match unit {
ReadUnit::Character(character) => material.push(ascii_byte(character)?),
ReadUnit::Byte(byte) => material.push(byte),
}
}
Ok(material)
}
fn nul_terminated_material(body: &str) -> Result<Vec<u8>, LiteralReadCause> {
let mut material = Vec::new();
let mut buffer = [0u8; 4];
for unit in units(body)? {
match unit {
ReadUnit::Character(character) => {
material.extend_from_slice(character.encode_utf8(&mut buffer).as_bytes());
}
ReadUnit::Byte(byte) => material.push(byte),
}
}
Ok(material)
}
fn one_character(body: &str) -> Result<char, LiteralReadCause> {
match units(body)?.as_slice() {
[ReadUnit::Character(character)] => Ok(*character),
[ReadUnit::Byte(byte)] if byte.is_ascii() => Ok(char::from(*byte)),
_ => Err(LiteralReadCause::NotReadable),
}
}
fn one_byte(body: &str) -> Result<u8, LiteralReadCause> {
match units(body)?.as_slice() {
[ReadUnit::Byte(byte)] => Ok(*byte),
[ReadUnit::Character(character)] => ascii_byte(*character),
_ => Err(LiteralReadCause::NotReadable),
}
}
fn ascii_byte(character: char) -> Result<u8, LiteralReadCause> {
if !character.is_ascii() {
return Err(LiteralReadCause::NotReadable);
}
u8::try_from(character).map_err(|_| LiteralReadCause::NotReadable)
}