use llvmkit_support::{Span, Spanned};
use super::ll_token::{
FpLit, HexFpKind, IntLit, Keyword, NumBase, Opcode, PrimitiveTy, QuotedNameKind, Sign, Token,
};
mod escape;
mod keywords;
pub const INT_TY_MIN_BITS: u64 = 1;
pub const INT_TY_MAX_BITS: u64 = (1u64 << 24) - 1;
#[derive(Clone, PartialEq, Eq, Hash, Debug, thiserror::Error)]
pub enum LexError {
#[error("unterminated /* */ block comment")]
UnterminatedBlockComment { span: Span },
#[error("end of file in string constant")]
UnterminatedString { span: Span },
#[error("end of file in {kind:?} name")]
UnterminatedQuotedName { kind: QuotedNameKind, span: Span },
#[error("NUL character is not allowed in names")]
NulInName { span: Span },
#[error("invalid value number (does not fit in u32)")]
IdOverflow { span: Span },
#[error("constant bigger than 64 bits detected")]
IntegerOverflow64 { span: Span },
#[error("constant bigger than 128 bits detected")]
IntegerOverflow128 { span: Span },
#[error("bitwidth for integer type out of range (got {width}, must be 1..={max})")]
IntegerWidthOutOfRange { width: u64, max: u32, span: Span },
#[error("hexadecimal constant too large for {target:?} (16-bit)")]
HexFpTooLarge { target: HexFpKind, span: Span },
#[error("invalid token")]
UnknownToken { span: Span },
#[error("expected '*' after '/' to start block comment")]
StraySlash { span: Span },
}
impl LexError {
pub fn span(&self) -> Span {
match self {
LexError::UnterminatedBlockComment { span }
| LexError::UnterminatedString { span }
| LexError::UnterminatedQuotedName { span, .. }
| LexError::NulInName { span }
| LexError::IdOverflow { span }
| LexError::IntegerOverflow64 { span }
| LexError::IntegerOverflow128 { span }
| LexError::IntegerWidthOutOfRange { span, .. }
| LexError::HexFpTooLarge { span, .. }
| LexError::UnknownToken { span }
| LexError::StraySlash { span } => *span,
}
}
}
#[derive(Clone, Debug)]
pub struct Lexer<'src> {
src: &'src [u8],
pos: usize,
tok_start: usize,
prev_tok_end: usize,
pub ignore_colon_in_idents: bool,
}
impl<'src> Lexer<'src> {
#[inline]
pub fn new(src: &'src [u8]) -> Self {
Self {
src,
pos: 0,
tok_start: 0,
prev_tok_end: 0,
ignore_colon_in_idents: false,
}
}
#[inline]
pub fn source(&self) -> &'src [u8] {
self.src
}
#[inline]
pub fn position(&self) -> u32 {
self.pos as u32
}
#[inline]
pub fn prev_token_end(&self) -> u32 {
self.prev_tok_end as u32
}
#[inline]
fn peek(&self) -> Option<u8> {
self.src.get(self.pos).copied()
}
#[inline]
fn peek_at(&self, n: usize) -> Option<u8> {
self.src.get(self.pos + n).copied()
}
#[inline]
fn bump(&mut self) -> Option<u8> {
let b = self.peek()?;
self.pos += 1;
Some(b)
}
#[inline]
fn span_from(&self, start: usize) -> Span {
Span::new(start as u32, self.pos as u32)
}
#[inline]
fn current_span(&self) -> Span {
self.span_from(self.tok_start)
}
#[inline]
fn spanned<T>(&self, value: T, span: Span) -> Spanned<T> {
Spanned::new(value, span)
}
pub fn next_token(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
loop {
self.prev_tok_end = self.pos;
self.tok_start = self.pos;
let Some(c) = self.peek() else {
let span = Span::empty_at(self.pos as u32);
return Ok(self.spanned(Token::Eof, span));
};
match c {
b' ' | b'\t' | b'\n' | b'\r' | 0 => {
self.bump();
continue;
}
b';' => {
self.skip_line_comment();
continue;
}
b'/' => {
self.bump();
self.skip_block_comment()?;
continue;
}
b'=' => {
self.bump();
return Ok(self.spanned(Token::Equal, self.current_span()));
}
b',' => {
self.bump();
return Ok(self.spanned(Token::Comma, self.current_span()));
}
b'*' => {
self.bump();
return Ok(self.spanned(Token::Star, self.current_span()));
}
b'[' => {
self.bump();
return Ok(self.spanned(Token::LSquare, self.current_span()));
}
b']' => {
self.bump();
return Ok(self.spanned(Token::RSquare, self.current_span()));
}
b'{' => {
self.bump();
return Ok(self.spanned(Token::LBrace, self.current_span()));
}
b'}' => {
self.bump();
return Ok(self.spanned(Token::RBrace, self.current_span()));
}
b'<' => {
self.bump();
return Ok(self.spanned(Token::Less, self.current_span()));
}
b'>' => {
self.bump();
return Ok(self.spanned(Token::Greater, self.current_span()));
}
b'(' => {
self.bump();
return Ok(self.spanned(Token::LParen, self.current_span()));
}
b')' => {
self.bump();
return Ok(self.spanned(Token::RParen, self.current_span()));
}
b'|' => {
self.bump();
return Ok(self.spanned(Token::Bar, self.current_span()));
}
b':' => {
self.bump();
return Ok(self.spanned(Token::Colon, self.current_span()));
}
b'+' => return self.lex_positive(),
b'-' | b'0'..=b'9' => return self.lex_digit_or_negative(),
b'@' => return self.lex_var(QuotedNameKind::Global),
b'%' => return self.lex_var(QuotedNameKind::Local),
b'$' => return self.lex_dollar(),
b'!' => return self.lex_exclaim(),
b'^' => return self.lex_caret(),
b'#' => return self.lex_hash(),
b'"' => return self.lex_quote(),
b'.' => return self.lex_dot(),
c if c.is_ascii_alphabetic() || c == b'_' => return self.lex_identifier(),
_ => {
self.bump();
return Err(LexError::UnknownToken {
span: self.current_span(),
});
}
}
}
}
fn skip_line_comment(&mut self) {
while let Some(b) = self.peek() {
if b == b'\n' || b == b'\r' {
return;
}
self.bump();
}
}
fn skip_block_comment(&mut self) -> Result<(), LexError> {
match self.bump() {
Some(b'*') => {}
_ => {
return Err(LexError::StraySlash {
span: self.span_from(self.tok_start),
});
}
}
loop {
match self.bump() {
None => {
return Err(LexError::UnterminatedBlockComment {
span: self.span_from(self.tok_start),
});
}
Some(b'*') => match self.peek() {
Some(b'/') => {
self.bump();
return Ok(());
}
Some(_) => continue,
None => {
return Err(LexError::UnterminatedBlockComment {
span: self.span_from(self.tok_start),
});
}
},
Some(_) => {}
}
}
}
fn lex_var(&mut self, kind: QuotedNameKind) -> Result<Spanned<Token<'src>>, LexError> {
self.bump();
if self.peek() == Some(b'"') {
return self.lex_quoted_name(kind);
}
let name_start = self.pos;
if let Some(b) = self.peek()
&& is_unquoted_name_start(b)
{
self.bump();
while let Some(b) = self.peek() {
if is_unquoted_name_cont(b) {
self.bump();
} else {
break;
}
}
let name = &self.src[name_start..self.pos];
let token = match kind {
QuotedNameKind::Global => Token::GlobalVar(std::borrow::Cow::Borrowed(name)),
QuotedNameKind::Local => Token::LocalVar(std::borrow::Cow::Borrowed(name)),
QuotedNameKind::Comdat => Token::ComdatVar(std::borrow::Cow::Borrowed(name)),
QuotedNameKind::String | QuotedNameKind::Metadata => {
unreachable!("lex_var handles only Global/Local/Comdat")
}
};
return Ok(self.spanned(token, self.current_span()));
}
if matches!(self.peek(), Some(b'0'..=b'9')) {
let id = self.lex_uint()?;
let span = self.current_span();
let token = match kind {
QuotedNameKind::Global => Token::GlobalId(id),
QuotedNameKind::Local => Token::LocalVarId(id),
_ => unreachable!("only @42 / %42 reach this branch"),
};
return Ok(self.spanned(token, span));
}
Err(LexError::UnknownToken {
span: self.current_span(),
})
}
fn lex_dollar(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
if let Some(end) = is_label_tail(self.src, self.pos) {
let content = &self.src[self.tok_start..end - 1];
self.pos = end;
return Ok(self.spanned(
Token::LabelStr(std::borrow::Cow::Borrowed(content)),
self.current_span(),
));
}
self.bump();
if self.peek() == Some(b'"') {
return self.lex_quoted_name(QuotedNameKind::Comdat);
}
let name_start = self.pos;
if let Some(b) = self.peek()
&& is_unquoted_name_start(b)
{
self.bump();
while let Some(b) = self.peek() {
if is_unquoted_name_cont(b) {
self.bump();
} else {
break;
}
}
let name = &self.src[name_start..self.pos];
return Ok(self.spanned(
Token::ComdatVar(std::borrow::Cow::Borrowed(name)),
self.current_span(),
));
}
Err(LexError::UnknownToken {
span: self.current_span(),
})
}
fn lex_exclaim(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
self.bump();
let name_start = self.pos;
let Some(first) = self.peek() else {
return Ok(self.spanned(Token::Exclaim, self.current_span()));
};
if !is_metadata_name_start(first) {
return Ok(self.spanned(Token::Exclaim, self.current_span()));
}
self.bump();
while let Some(b) = self.peek() {
if is_metadata_name_cont(b) {
self.bump();
} else {
break;
}
}
let raw = &self.src[name_start..self.pos];
let decoded = escape::unescape(raw);
Ok(self.spanned(Token::MetadataVar(decoded), self.current_span()))
}
fn lex_caret(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
self.bump(); if !matches!(self.peek(), Some(b'0'..=b'9')) {
return Err(LexError::UnknownToken {
span: self.current_span(),
});
}
let id = self.lex_uint()?;
Ok(self.spanned(Token::SummaryId(id), self.current_span()))
}
fn lex_hash(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
self.bump(); if matches!(self.peek(), Some(b'0'..=b'9')) {
let id = self.lex_uint()?;
return Ok(self.spanned(Token::AttrGrpId(id), self.current_span()));
}
Ok(self.spanned(Token::Hash, self.current_span()))
}
fn lex_quote(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
let raw = self.read_quoted_payload(QuotedNameKind::String)?;
let decoded = escape::unescape(raw);
if self.peek() == Some(b':') {
self.bump();
if decoded.contains(&0) {
return Err(LexError::NulInName {
span: self.current_span(),
});
}
return Ok(self.spanned(Token::LabelStr(decoded), self.current_span()));
}
Ok(self.spanned(Token::StringConstant(decoded), self.current_span()))
}
fn lex_quoted_name(&mut self, kind: QuotedNameKind) -> Result<Spanned<Token<'src>>, LexError> {
let raw = self.read_quoted_payload(kind)?;
let decoded = escape::unescape(raw);
if decoded.contains(&0) {
return Err(LexError::NulInName {
span: self.current_span(),
});
}
let token = match kind {
QuotedNameKind::Global => Token::GlobalVar(decoded),
QuotedNameKind::Local => Token::LocalVar(decoded),
QuotedNameKind::Comdat => Token::ComdatVar(decoded),
QuotedNameKind::Metadata | QuotedNameKind::String => {
unreachable!("metadata and string use direct paths")
}
};
Ok(self.spanned(token, self.current_span()))
}
fn read_quoted_payload(&mut self, kind: QuotedNameKind) -> Result<&'src [u8], LexError> {
debug_assert_eq!(self.peek(), Some(b'"'));
self.bump(); let start = self.pos;
loop {
match self.bump() {
None => {
let span = self.current_span();
return Err(match kind {
QuotedNameKind::String => LexError::UnterminatedString { span },
_ => LexError::UnterminatedQuotedName { kind, span },
});
}
Some(b'"') => {
return Ok(&self.src[start..self.pos - 1]);
}
_ => {}
}
}
}
fn lex_uint(&mut self) -> Result<u32, LexError> {
let digits_start = self.pos;
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.bump();
}
let digits = ascii_str(&self.src[digits_start..self.pos]);
digits.parse::<u32>().map_err(|_| LexError::IdOverflow {
span: self.span_from(self.tok_start),
})
}
fn lex_dot(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
if let Some(end) = is_label_tail(self.src, self.pos + 1) {
let content = &self.src[self.pos..end - 1];
self.pos = end;
return Ok(self.spanned(
Token::LabelStr(std::borrow::Cow::Borrowed(content)),
self.current_span(),
));
}
if self.peek_at(0) == Some(b'.')
&& self.peek_at(1) == Some(b'.')
&& self.peek_at(2) == Some(b'.')
{
self.pos += 3;
return Ok(self.spanned(Token::DotDotDot, self.current_span()));
}
self.bump();
Err(LexError::UnknownToken {
span: self.current_span(),
})
}
fn lex_digit_or_negative(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
let first = self.peek().expect("caller verified non-empty");
if first == b'-' && !matches!(self.peek_at(1), Some(b'0'..=b'9')) {
self.bump(); if let Some(end) = is_label_tail(self.src, self.pos) {
let content = &self.src[self.tok_start..end - 1];
self.pos = end;
return Ok(self.spanned(
Token::LabelStr(std::borrow::Cow::Borrowed(content)),
self.current_span(),
));
}
return Err(LexError::UnknownToken {
span: self.current_span(),
});
}
let sign = if first == b'-' {
self.bump();
Sign::Neg
} else {
Sign::Pos
};
let digits_start = self.pos;
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.bump();
}
if matches!(sign, Sign::Pos) && self.peek() == Some(b':') {
let content = &self.src[self.tok_start..self.pos];
self.bump(); return Ok(self.spanned(
Token::LabelStr(std::borrow::Cow::Borrowed(content)),
self.current_span(),
));
}
if matches!(self.peek(), Some(c) if is_label_char(c) || c == b':')
&& let Some(end) = is_label_tail(self.src, self.pos)
{
let content = &self.src[self.tok_start..end - 1];
self.pos = end;
return Ok(self.spanned(
Token::LabelStr(std::borrow::Cow::Borrowed(content)),
self.current_span(),
));
}
if self.peek() != Some(b'.') {
if matches!(sign, Sign::Pos)
&& self.src.get(self.tok_start) == Some(&b'0')
&& self.src.get(self.tok_start + 1) == Some(&b'x')
{
return self.lex_hex_fp();
}
let digits = ascii_str(&self.src[digits_start..self.pos]);
return Ok(self.spanned(
Token::IntegerLit(IntLit {
sign,
base: NumBase::Dec,
digits,
}),
self.current_span(),
));
}
self.consume_fp_tail()?;
let lex = ascii_str(&self.src[self.tok_start..self.pos]);
Ok(self.spanned(Token::FloatLit(FpLit::Decimal(lex)), self.current_span()))
}
fn lex_positive(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
self.bump(); if !matches!(self.peek(), Some(b'0'..=b'9')) {
return Err(LexError::UnknownToken {
span: self.current_span(),
});
}
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.bump();
}
if self.peek() != Some(b'.') {
return Err(LexError::UnknownToken {
span: self.current_span(),
});
}
self.consume_fp_tail()?;
let lex = ascii_str(&self.src[self.tok_start..self.pos]);
Ok(self.spanned(Token::FloatLit(FpLit::Decimal(lex)), self.current_span()))
}
fn consume_fp_tail(&mut self) -> Result<(), LexError> {
debug_assert_eq!(self.peek(), Some(b'.'));
self.bump(); while matches!(self.peek(), Some(b'0'..=b'9')) {
self.bump();
}
if matches!(self.peek(), Some(b'e' | b'E')) {
let after_e = self.peek_at(1);
let after_sign = self.peek_at(2);
let has_exp = matches!(after_e, Some(b'0'..=b'9'))
|| (matches!(after_e, Some(b'+' | b'-'))
&& matches!(after_sign, Some(b'0'..=b'9')));
if has_exp {
self.bump(); if matches!(self.peek(), Some(b'+' | b'-')) {
self.bump();
}
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.bump();
}
}
}
Ok(())
}
fn lex_hex_fp(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
self.pos = self.tok_start + 2;
let kind = match self.peek() {
Some(b @ (b'K' | b'L' | b'M' | b'H' | b'R')) => {
self.bump();
b
}
_ => b'J', };
let digits_start = self.pos;
if !matches!(self.peek(), Some(c) if c.is_ascii_hexdigit()) {
self.pos = self.tok_start + 1;
return Err(LexError::UnknownToken {
span: self.current_span(),
});
}
while matches!(self.peek(), Some(c) if c.is_ascii_hexdigit()) {
self.bump();
}
let digits = ascii_str(&self.src[digits_start..self.pos]);
let token = match kind {
b'J' => Token::FloatLit(FpLit::HexDouble(digits)),
b'K' => Token::FloatLit(FpLit::HexX87(digits)),
b'L' => Token::FloatLit(FpLit::HexQuad(digits)),
b'M' => Token::FloatLit(FpLit::HexPpc128(digits)),
b'H' => {
self.check_hex16_fits(digits, HexFpKind::Half)?;
Token::FloatLit(FpLit::HexHalf(digits))
}
b'R' => {
self.check_hex16_fits(digits, HexFpKind::BFloat)?;
Token::FloatLit(FpLit::HexBFloat(digits))
}
_ => unreachable!(),
};
Ok(self.spanned(token, self.current_span()))
}
fn check_hex16_fits(&self, digits: &str, target: HexFpKind) -> Result<(), LexError> {
u16::from_str_radix(digits, 16)
.map(|_| ())
.map_err(|_| LexError::HexFpTooLarge {
target,
span: self.current_span(),
})
}
fn lex_identifier(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
let first = self.bump().expect("caller verified non-empty");
let mut keyword_end: Option<usize> = None;
while let Some(b) = self.peek() {
if !is_label_char(b) {
break;
}
if keyword_end.is_none() && !b.is_ascii_alphanumeric() && b != b'_' {
keyword_end = Some(self.pos);
}
self.bump();
}
if !self.ignore_colon_in_idents && self.peek() == Some(b':') {
let content = &self.src[self.tok_start..self.pos];
self.bump(); return Ok(self.spanned(
Token::LabelStr(std::borrow::Cow::Borrowed(content)),
self.current_span(),
));
}
if first == b'i' {
let mut int_end = self.tok_start + 1;
while int_end < self.pos && self.src[int_end].is_ascii_digit() {
int_end += 1;
}
if int_end > self.tok_start + 1 {
self.pos = int_end;
let digits = ascii_str(&self.src[self.tok_start + 1..self.pos]);
let width = digits.parse::<u64>().unwrap_or(u64::MAX);
if !(INT_TY_MIN_BITS..=INT_TY_MAX_BITS).contains(&width) {
return Err(LexError::IntegerWidthOutOfRange {
width,
max: INT_TY_MAX_BITS as u32,
span: self.current_span(),
});
}
let nz = std::num::NonZeroU32::new(width as u32)
.expect("width validated within 1..=INT_TY_MAX_BITS");
return Ok(self.spanned(
Token::PrimitiveType(PrimitiveTy::Integer(nz)),
self.current_span(),
));
}
}
let kw_end = keyword_end.unwrap_or(self.pos);
self.pos = kw_end;
let word = &self.src[self.tok_start..self.pos];
let span = self.current_span();
if let Some(tok) = self.classify_prefixed(word) {
return Ok(self.spanned(tok, span));
}
if let Some(tok) = keywords::classify_word(word) {
return Ok(self.spanned(tok, span));
}
if let Some(tok) = self.classify_hex_apsint(word)? {
return Ok(self.spanned(tok, span));
}
if word.len() > 2 && word.starts_with(b"cc") && word[2..].iter().all(|c| c.is_ascii_digit())
{
self.pos = self.tok_start + 2;
return Ok(self.spanned(
Token::Kw(Keyword::Cc),
Span::new(self.tok_start as u32, self.pos as u32),
));
}
self.pos = self.tok_start + 1;
Err(LexError::UnknownToken {
span: self.current_span(),
})
}
fn classify_prefixed(&self, word: &'src [u8]) -> Option<Token<'src>> {
let s = ascii_str(word);
if let Some(suffix) = s.strip_prefix("dbg_") {
return match suffix {
"value" | "declare" | "assign" | "label" | "declare_value" => {
let suffix_str = ascii_str(&word[4..]);
Some(Token::DbgRecordType(suffix_str))
}
_ => None,
};
}
if let Some(rest) = s.strip_prefix("DW_") {
let token = match rest.split_once('_').map(|(t, _)| t) {
Some("TAG") => Token::DwarfTag(s),
Some("ATE") => Token::DwarfAttEncoding(s),
Some("VIRTUALITY") => Token::DwarfVirtuality(s),
Some("LANG") => Token::DwarfLang(s),
Some("LNAME") => Token::DwarfSourceLangName(s),
Some("CC") => Token::DwarfCC(s),
Some("OP") => Token::DwarfOp(s),
Some("MACINFO") => Token::DwarfMacinfo(s),
Some("APPLE") if rest.starts_with("APPLE_ENUM_KIND_") => Token::DwarfEnumKind(s),
_ => return None,
};
return Some(token);
}
if matches!(
s,
"DIFile"
| "DICompileUnit"
| "DISubprogram"
| "DILocation"
| "DILocalVariable"
| "DIBasicType"
| "DIDerivedType"
| "DICompositeType"
| "DISubrange"
| "DINamespace"
| "DIExpression"
| "DIGlobalVariable"
| "DIGlobalVariableExpression"
| "DISubroutineType"
| "DIEnumerator"
| "DIModule"
| "DITemplateTypeParameter"
| "DITemplateValueParameter"
) {
return Some(Token::SpecializedMetadata(s));
}
if s.starts_with("DIFlag") {
return Some(Token::DiFlag(s));
}
if s.starts_with("DISPFlag") {
return Some(Token::DiSpFlag(s));
}
if s.starts_with("CSK_") {
return Some(Token::ChecksumKind(s));
}
match s {
"NoDebug" | "FullDebug" | "LineTablesOnly" | "DebugDirectivesOnly" => {
Some(Token::EmissionKind(s))
}
"GNU" | "Apple" | "None" | "Default" => Some(Token::NameTableKind(s)),
"Binary" | "Decimal" | "Rational" => Some(Token::FixedPointKind(s)),
_ => None,
}
}
fn classify_hex_apsint(&self, word: &'src [u8]) -> Result<Option<Token<'src>>, LexError> {
if word.len() < 4 {
return Ok(None);
}
let base = match word[0] {
b's' => NumBase::HexSigned,
b'u' => NumBase::HexUnsigned,
_ => return Ok(None),
};
if word[1] != b'0' || word[2] != b'x' {
return Ok(None);
}
if !word[3..].iter().all(|c| c.is_ascii_hexdigit()) {
return Ok(None);
}
let digits = ascii_str(&word[3..]);
Ok(Some(Token::IntegerLit(IntLit {
sign: Sign::Pos,
base,
digits,
})))
}
}
impl<'src> From<&'src [u8]> for Lexer<'src> {
#[inline]
fn from(src: &'src [u8]) -> Self {
Self::new(src)
}
}
impl<'src> From<&'src str> for Lexer<'src> {
#[inline]
fn from(src: &'src str) -> Self {
Self::new(src.as_bytes())
}
}
impl<'src> Iterator for Lexer<'src> {
type Item = Result<Spanned<Token<'src>>, LexError>;
fn next(&mut self) -> Option<Self::Item> {
match self.next_token() {
Ok(spanned) if matches!(spanned.value, Token::Eof) => None,
other => Some(other),
}
}
}
#[inline]
fn is_label_char(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'-' || b == b'$' || b == b'.' || b == b'_'
}
#[inline]
fn is_unquoted_name_start(b: u8) -> bool {
b.is_ascii_alphabetic() || b == b'-' || b == b'$' || b == b'.' || b == b'_'
}
#[inline]
fn is_unquoted_name_cont(b: u8) -> bool {
is_label_char(b)
}
#[inline]
fn is_metadata_name_start(b: u8) -> bool {
is_unquoted_name_start(b) || b == b'\\'
}
#[inline]
fn is_metadata_name_cont(b: u8) -> bool {
is_label_char(b) || b == b'\\'
}
#[inline]
fn ascii_str(bytes: &[u8]) -> &str {
std::str::from_utf8(bytes).expect("lexer regex guarantees ASCII")
}
#[inline]
fn is_label_tail(src: &[u8], mut pos: usize) -> Option<usize> {
while let Some(&b) = src.get(pos) {
if b == b':' {
return Some(pos + 1);
}
if !is_label_char(b) {
return None;
}
pos += 1;
}
None
}
#[cfg(test)]
#[path = "ll_lexer_tests.rs"]
mod tests;