Source code

Revision control

Copy as Markdown

Other Tools

use crate::{
BoolLit,
Buffer,
ByteLit,
ByteStringLit,
CharLit,
ParseError,
FloatLit,
IntegerLit,
Literal,
StringLit,
err::{perr, ParseErrorKind::{*, self}},
};
pub fn parse<B: Buffer>(input: B) -> Result<Literal<B>, ParseError> {
let (first, rest) = input.as_bytes().split_first().ok_or(perr(None, Empty))?;
let second = input.as_bytes().get(1).copied();
match first {
b'f' if &*input == "false" => Ok(Literal::Bool(BoolLit::False)),
b't' if &*input == "true" => Ok(Literal::Bool(BoolLit::True)),
// A number literal (integer or float).
b'0'..=b'9' => {
// To figure out whether this is a float or integer, we do some
// quick inspection here. Yes, this is technically duplicate
// work with what is happening in the integer/float parse
// methods, but it makes the code way easier for now and won't
// be a huge performance loss.
//
// The first non-decimal char in a float literal must
// be '.', 'e' or 'E'.
match input.as_bytes().get(1 + end_dec_digits(rest)) {
Some(b'.') | Some(b'e') | Some(b'E')
=> FloatLit::parse(input).map(Literal::Float),
_ => IntegerLit::parse(input).map(Literal::Integer),
}
},
b'\'' => CharLit::parse(input).map(Literal::Char),
b'"' | b'r' => StringLit::parse(input).map(Literal::String),
b'b' if second == Some(b'\'') => ByteLit::parse(input).map(Literal::Byte),
b'b' if second == Some(b'r') || second == Some(b'"')
=> ByteStringLit::parse(input).map(Literal::ByteString),
_ => Err(perr(None, InvalidLiteral)),
}
}
pub(crate) fn first_byte_or_empty(s: &str) -> Result<u8, ParseError> {
s.as_bytes().get(0).copied().ok_or(perr(None, Empty))
}
/// Returns the index of the first non-underscore, non-decimal digit in `input`,
/// or the `input.len()` if all characters are decimal digits.
pub(crate) fn end_dec_digits(input: &[u8]) -> usize {
input.iter()
.position(|b| !matches!(b, b'_' | b'0'..=b'9'))
.unwrap_or(input.len())
}
pub(crate) fn hex_digit_value(digit: u8) -> Option<u8> {
match digit {
b'0'..=b'9' => Some(digit - b'0'),
b'a'..=b'f' => Some(digit - b'a' + 10),
b'A'..=b'F' => Some(digit - b'A' + 10),
_ => None,
}
}
/// Makes sure that `s` is a valid literal suffix.
pub(crate) fn check_suffix(s: &str) -> Result<(), ParseErrorKind> {
if s.is_empty() {
return Ok(());
}
let mut chars = s.chars();
let first = chars.next().unwrap();
let rest = chars.as_str();
if first == '_' && rest.is_empty() {
return Err(InvalidSuffix);
}
// This is just an extra check to improve the error message. If the first
// character of the "suffix" is already some invalid ASCII
// char, "unexpected character" seems like the more fitting error.
if first.is_ascii() && !(first.is_ascii_alphabetic() || first == '_') {
return Err(UnexpectedChar);
}
// Proper check is optional as it's not really necessary in proc macro
// context.
#[cfg(feature = "check_suffix")]
fn is_valid_suffix(first: char, rest: &str) -> bool {
use unicode_xid::UnicodeXID;
(first == '_' || first.is_xid_start())
&& rest.chars().all(|c| c.is_xid_continue())
}
// When avoiding the dependency on `unicode_xid`, we just do a best effort
// to catch the most common errors.
#[cfg(not(feature = "check_suffix"))]
fn is_valid_suffix(first: char, rest: &str) -> bool {
if first.is_ascii() && !(first.is_ascii_alphabetic() || first == '_') {
return false;
}
for c in rest.chars() {
if c.is_ascii() && !(c.is_ascii_alphanumeric() || c == '_') {
return false;
}
}
true
}
if is_valid_suffix(first, rest) {
Ok(())
} else {
Err(InvalidSuffix)
}
}