use crate::capture::{Pos, Source, SourceRange};
use crate::diag::Diagnostic;
use crate::{Dialect, Options, Standard};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
#[allow(missing_docs)]
pub enum Keyword {
Auto,
Break,
Case,
Char,
Const,
Continue,
Default,
Do,
Double,
Else,
Enum,
Extern,
Float,
For,
Goto,
If,
Inline,
Int,
Long,
Register,
Restrict,
Return,
Short,
Signed,
Sizeof,
Static,
Struct,
Switch,
Typedef,
Union,
Unsigned,
Void,
Volatile,
While,
Bool,
Complex,
Imaginary,
Alignas,
Alignof,
Atomic,
Generic,
Noreturn,
StaticAssert,
ThreadLocal,
BitInt,
BoolName,
True,
False,
Nullptr,
Typeof,
TypeofUnqual,
Constexpr,
StaticAssertName,
AlignofName,
AlignasName,
ThreadLocalName,
Attribute,
Extension,
AlignofGnu,
TypeofGnu,
TypeofUnqualGnu,
Asm,
Label,
AutoType,
ThreadGnu,
Int128,
RealGnu,
ImagGnu,
InlineGnu,
RestrictGnu,
}
impl Keyword {
pub fn as_str(self) -> &'static str {
use Keyword::*;
match self {
Auto => "auto",
Break => "break",
Case => "case",
Char => "char",
Const => "const",
Continue => "continue",
Default => "default",
Do => "do",
Double => "double",
Else => "else",
Enum => "enum",
Extern => "extern",
Float => "float",
For => "for",
Goto => "goto",
If => "if",
Inline => "inline",
Int => "int",
Long => "long",
Register => "register",
Restrict => "restrict",
Return => "return",
Short => "short",
Signed => "signed",
Sizeof => "sizeof",
Static => "static",
Struct => "struct",
Switch => "switch",
Typedef => "typedef",
Union => "union",
Unsigned => "unsigned",
Void => "void",
Volatile => "volatile",
While => "while",
Bool => "_Bool",
Complex => "_Complex",
Imaginary => "_Imaginary",
Alignas => "_Alignas",
Alignof => "_Alignof",
Atomic => "_Atomic",
Generic => "_Generic",
Noreturn => "_Noreturn",
StaticAssert => "_Static_assert",
ThreadLocal => "_Thread_local",
BitInt => "_BitInt",
BoolName => "bool",
True => "true",
False => "false",
Nullptr => "nullptr",
Typeof => "typeof",
TypeofUnqual => "typeof_unqual",
Constexpr => "constexpr",
StaticAssertName => "static_assert",
AlignofName => "alignof",
AlignasName => "alignas",
ThreadLocalName => "thread_local",
Attribute => "__attribute__",
Extension => "__extension__",
AlignofGnu => "__alignof__",
TypeofGnu => "__typeof__",
TypeofUnqualGnu => "__typeof_unqual__",
Asm => "__asm__",
Label => "__label__",
AutoType => "__auto_type",
ThreadGnu => "__thread",
Int128 => "__int128",
RealGnu => "__real__",
ImagGnu => "__imag__",
InlineGnu => "__inline__",
RestrictGnu => "__restrict__",
}
}
pub fn is_gnu(self) -> bool {
use Keyword::*;
matches!(
self,
Attribute
| Extension
| AlignofGnu
| TypeofGnu
| TypeofUnqualGnu
| Asm
| Label
| AutoType
| ThreadGnu
| Int128
| RealGnu
| ImagGnu
| InlineGnu
| RestrictGnu
)
}
pub fn since(self) -> Standard {
use Keyword::*;
match self {
Alignas | Alignof | Atomic | Generic | Noreturn | StaticAssert | ThreadLocal => {
Standard::C11
}
BitInt | BoolName | True | False | Nullptr | Typeof | TypeofUnqual | Constexpr
| StaticAssertName | AlignofName | AlignasName | ThreadLocalName => Standard::C23,
Inline | Restrict | Bool | Complex | Imaginary => Standard::C99,
_ => Standard::C89,
}
}
pub fn from_str(s: &str, standard: Standard) -> Option<Keyword> {
use Keyword::*;
if standard >= Standard::C23 {
let c23 = match s {
"bool" => Some(BoolName),
"true" => Some(True),
"false" => Some(False),
"nullptr" => Some(Nullptr),
"typeof" => Some(Typeof),
"typeof_unqual" => Some(TypeofUnqual),
"constexpr" => Some(Constexpr),
"static_assert" => Some(StaticAssertName),
"alignof" => Some(AlignofName),
"alignas" => Some(AlignasName),
"thread_local" => Some(ThreadLocalName),
_ => None,
};
if c23.is_some() {
return c23;
}
}
Some(match s {
"_Alignas" => Alignas,
"_Alignof" => Alignof,
"_Atomic" => Atomic,
"_Generic" => Generic,
"_Noreturn" => Noreturn,
"_Static_assert" => StaticAssert,
"_Thread_local" => ThreadLocal,
"_BitInt" => BitInt,
"auto" => Auto,
"break" => Break,
"case" => Case,
"char" => Char,
"const" => Const,
"continue" => Continue,
"default" => Default,
"do" => Do,
"double" => Double,
"else" => Else,
"enum" => Enum,
"extern" => Extern,
"float" => Float,
"for" => For,
"goto" => Goto,
"if" => If,
"inline" => Inline,
"int" => Int,
"long" => Long,
"register" => Register,
"restrict" => Restrict,
"return" => Return,
"short" => Short,
"signed" => Signed,
"sizeof" => Sizeof,
"static" => Static,
"struct" => Struct,
"switch" => Switch,
"typedef" => Typedef,
"union" => Union,
"unsigned" => Unsigned,
"void" => Void,
"volatile" => Volatile,
"while" => While,
"_Bool" => Bool,
"_Complex" => Complex,
"_Imaginary" => Imaginary,
_ => return None,
})
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
#[allow(missing_docs)]
pub enum Punct {
LBracket,
RBracket,
LParen,
RParen,
LBrace,
RBrace,
Dot,
Arrow,
PlusPlus,
MinusMinus,
Amp,
Star,
Plus,
Minus,
Tilde,
Bang,
Slash,
Percent,
Shl,
Shr,
Lt,
Gt,
Le,
Ge,
EqEq,
Ne,
Caret,
Pipe,
AmpAmp,
PipePipe,
Question,
Colon,
Semi,
Ellipsis,
Assign,
StarAssign,
SlashAssign,
PercentAssign,
PlusAssign,
MinusAssign,
ShlAssign,
ShrAssign,
AmpAssign,
CaretAssign,
PipeAssign,
Comma,
Hash,
HashHash,
}
impl Punct {
pub fn as_str(self) -> &'static str {
use Punct::*;
match self {
LBracket => "[",
RBracket => "]",
LParen => "(",
RParen => ")",
LBrace => "{",
RBrace => "}",
Dot => ".",
Arrow => "->",
PlusPlus => "++",
MinusMinus => "--",
Amp => "&",
Star => "*",
Plus => "+",
Minus => "-",
Tilde => "~",
Bang => "!",
Slash => "/",
Percent => "%",
Shl => "<<",
Shr => ">>",
Lt => "<",
Gt => ">",
Le => "<=",
Ge => ">=",
EqEq => "==",
Ne => "!=",
Caret => "^",
Pipe => "|",
AmpAmp => "&&",
PipePipe => "||",
Question => "?",
Colon => ":",
Semi => ";",
Ellipsis => "...",
Assign => "=",
StarAssign => "*=",
SlashAssign => "/=",
PercentAssign => "%=",
PlusAssign => "+=",
MinusAssign => "-=",
ShlAssign => "<<=",
ShrAssign => ">>=",
AmpAssign => "&=",
CaretAssign => "^=",
PipeAssign => "|=",
Comma => ",",
Hash => "#",
HashHash => "##",
}
}
}
const PUNCTUATORS: &[(&str, Punct)] = &[
("%:%:", Punct::HashHash),
("...", Punct::Ellipsis),
("<<=", Punct::ShlAssign),
(">>=", Punct::ShrAssign),
("->", Punct::Arrow),
("++", Punct::PlusPlus),
("--", Punct::MinusMinus),
("<<", Punct::Shl),
(">>", Punct::Shr),
("<=", Punct::Le),
(">=", Punct::Ge),
("==", Punct::EqEq),
("!=", Punct::Ne),
("&&", Punct::AmpAmp),
("||", Punct::PipePipe),
("*=", Punct::StarAssign),
("/=", Punct::SlashAssign),
("%=", Punct::PercentAssign),
("+=", Punct::PlusAssign),
("-=", Punct::MinusAssign),
("&=", Punct::AmpAssign),
("^=", Punct::CaretAssign),
("|=", Punct::PipeAssign),
("##", Punct::HashHash),
("<:", Punct::LBracket),
(":>", Punct::RBracket),
("<%", Punct::LBrace),
("%>", Punct::RBrace),
("%:", Punct::Hash),
("[", Punct::LBracket),
("]", Punct::RBracket),
("(", Punct::LParen),
(")", Punct::RParen),
("{", Punct::LBrace),
("}", Punct::RBrace),
(".", Punct::Dot),
("&", Punct::Amp),
("*", Punct::Star),
("+", Punct::Plus),
("-", Punct::Minus),
("~", Punct::Tilde),
("!", Punct::Bang),
("/", Punct::Slash),
("%", Punct::Percent),
("<", Punct::Lt),
(">", Punct::Gt),
("^", Punct::Caret),
("|", Punct::Pipe),
("?", Punct::Question),
(":", Punct::Colon),
(";", Punct::Semi),
("=", Punct::Assign),
(",", Punct::Comma),
("#", Punct::Hash),
];
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum LongKind {
#[default]
None,
Long,
LongLong,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NumBase {
Binary,
Octal,
Decimal,
Hex,
}
impl NumBase {
pub fn radix(self) -> u32 {
match self {
NumBase::Binary => 2,
NumBase::Octal => 8,
NumBase::Decimal => 10,
NumBase::Hex => 16,
}
}
pub fn as_str(self) -> &'static str {
match self {
NumBase::Binary => "binary",
NumBase::Octal => "octal",
NumBase::Decimal => "decimal",
NumBase::Hex => "hexadecimal",
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct IntLit {
pub value: u128,
pub base: NumBase,
pub unsigned: bool,
pub long: LongKind,
pub text: String,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum FloatSuffix {
#[default]
None,
Float,
LongDouble,
}
#[derive(Clone, PartialEq, Debug)]
pub struct FloatLit {
pub value: f64,
pub suffix: FloatSuffix,
pub imaginary: bool,
pub hex: bool,
pub text: String,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct CharLit {
pub value: i64,
pub kind: StrKind,
pub text: String,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum StrKind {
Narrow,
Utf8,
Utf16,
Utf32,
Wide,
}
impl StrKind {
pub fn prefix(self) -> &'static str {
match self {
StrKind::Narrow => "",
StrKind::Utf8 => "u8",
StrKind::Utf16 => "u",
StrKind::Utf32 => "U",
StrKind::Wide => "L",
}
}
pub fn since(self, character: bool) -> Standard {
match self {
StrKind::Narrow | StrKind::Wide => Standard::C89,
StrKind::Utf8 if character => Standard::C23,
StrKind::Utf8 | StrKind::Utf16 | StrKind::Utf32 => Standard::C11,
}
}
fn is_bytes(self) -> bool {
matches!(self, StrKind::Narrow | StrKind::Utf8)
}
fn max_element(self, wide_bits: u32) -> u32 {
match self {
StrKind::Narrow | StrKind::Utf8 => 0xff,
StrKind::Utf16 => 0xffff,
StrKind::Wide if wide_bits <= 16 => 0xffff,
StrKind::Utf32 | StrKind::Wide => u32::MAX,
}
}
fn is_utf16(self, wide_bits: u32) -> bool {
self == StrKind::Utf16 || (self == StrKind::Wide && wide_bits <= 16)
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct StrLit {
pub kind: StrKind,
pub values: Vec<u32>,
pub text: String,
}
impl StrLit {
pub fn as_bytes(&self) -> Option<Vec<u8>> {
(self.kind == StrKind::Narrow).then(|| self.values.iter().map(|v| *v as u8).collect())
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum TokenKind {
Eof,
Ident(String),
Keyword(Keyword),
Int(IntLit),
Float(FloatLit),
Char(CharLit),
Str(StrLit),
Punct(Punct),
Error(String),
}
impl TokenKind {
pub fn describe(&self) -> String {
match self {
TokenKind::Eof => "end of input".to_owned(),
TokenKind::Ident(name) => format!("identifier '{name}'"),
TokenKind::Keyword(k) => format!("keyword '{}'", k.as_str()),
TokenKind::Int(_) => "integer constant".to_owned(),
TokenKind::Float(_) => "floating constant".to_owned(),
TokenKind::Char(_) => "character constant".to_owned(),
TokenKind::Str(_) => "string literal".to_owned(),
TokenKind::Punct(p) => format!("'{}'", p.as_str()),
TokenKind::Error(text) => format!("'{text}'"),
}
}
pub fn spelling(&self) -> &str {
match self {
TokenKind::Eof => "",
TokenKind::Ident(name) => name,
TokenKind::Keyword(k) => k.as_str(),
TokenKind::Int(lit) => &lit.text,
TokenKind::Float(lit) => &lit.text,
TokenKind::Char(lit) => &lit.text,
TokenKind::Str(lit) => &lit.text,
TokenKind::Punct(p) => p.as_str(),
TokenKind::Error(text) => text,
}
}
pub fn macro_name(&self) -> Option<&str> {
match self {
TokenKind::Ident(name) => Some(name),
TokenKind::Keyword(k) => Some(k.as_str()),
_ => None,
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct Token {
pub kind: TokenKind,
pub range: SourceRange,
pub bol: bool,
pub preceded_by_space: bool,
pub errors: Vec<Diagnostic>,
}
impl Token {
pub fn keyword(&self) -> Option<Keyword> {
match &self.kind {
TokenKind::Keyword(k) => Some(*k),
_ => None,
}
}
pub fn is_punct(&self, p: Punct) -> bool {
self.kind == TokenKind::Punct(p)
}
pub fn is_keyword(&self, k: Keyword) -> bool {
self.kind == TokenKind::Keyword(k)
}
pub fn is_eof(&self) -> bool {
self.kind == TokenKind::Eof
}
pub fn ident(&self) -> Option<&str> {
match &self.kind {
TokenKind::Ident(name) => Some(name),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct LexOptions {
pub standard: Standard,
pub gating: crate::Gating,
pub dollar_in_identifiers: bool,
pub trigraphs: bool,
pub wchar_bits: u32,
pub complex: bool,
}
impl LexOptions {
pub fn new(standard: Standard) -> Self {
Self {
standard,
gating: crate::Gating {
standard,
dialect: crate::Dialect::Iso,
},
dollar_in_identifiers: true,
trigraphs: trigraphs_enabled(standard, crate::Dialect::Iso),
wchar_bits: crate::TargetModel::host().wchar_bits,
complex: crate::COMPLEX_SUPPORTED,
}
}
}
impl From<&Options> for LexOptions {
fn from(o: &Options) -> Self {
Self {
standard: o.standard,
gating: o.gating(),
dollar_in_identifiers: o.dollar_in_identifiers,
trigraphs: trigraphs_enabled(o.standard, o.dialect),
wchar_bits: o.target.wchar_bits,
complex: o.complex,
}
}
}
pub fn trigraphs_enabled(standard: Standard, dialect: crate::Dialect) -> bool {
standard < Standard::C23 && !dialect.is_gnu()
}
const TRIGRAPHS: &[(u8, u8)] = &[
(b'=', b'#'),
(b'(', b'['),
(b'/', b'\\'),
(b')', b']'),
(b'\'', b'^'),
(b'<', b'{'),
(b'!', b'|'),
(b'>', b'}'),
(b'-', b'~'),
];
pub fn lex(source: &Source, options: &Options) -> Vec<Token> {
let file = source.map.file(source.root);
lex_file(file.text(), file.base(), &options.into())
}
const BYTE_ORDER_MARK: char = '\u{feff}';
pub fn lex_file(text: &str, base: Pos, options: &LexOptions) -> Vec<Token> {
let start = if text.starts_with(BYTE_ORDER_MARK) {
BYTE_ORDER_MARK.len_utf8()
} else {
0
};
lex_from(text, base, start, options)
}
pub fn lex_text(text: &str, base: Pos, options: &LexOptions) -> Vec<Token> {
lex_from(text, base, 0, options)
}
fn lex_from(text: &str, base: Pos, start: usize, options: &LexOptions) -> Vec<Token> {
Lexer {
text,
bytes: text.as_bytes(),
base,
pos: start,
options: *options,
pending: Vec::new(),
}
.run()
}
struct Lexer<'a> {
text: &'a str,
bytes: &'a [u8],
base: Pos,
pos: usize,
options: LexOptions,
pending: Vec<Diagnostic>,
}
impl<'a> Lexer<'a> {
fn range(&self, start: usize, end: usize) -> SourceRange {
SourceRange::new(self.base + start as Pos, self.base + end as Pos)
}
fn error(&mut self, range: SourceRange, message: impl Into<String>) {
self.pending.push(Diagnostic::error(range, message));
}
fn lexical_error(&mut self, range: SourceRange, message: impl Into<String>) {
self.pending
.push(Diagnostic::error(range, message).at_lexing());
}
fn warning(&mut self, range: SourceRange, message: impl Into<String>) {
self.pending.push(Diagnostic::warning(range, message));
}
fn peek(&self) -> Option<u8> {
self.bytes.get(self.pos).copied()
}
fn peek_at(&self, n: usize) -> Option<u8> {
self.bytes.get(self.pos + n).copied()
}
fn eof(&self) -> bool {
self.pos >= self.bytes.len()
}
fn run(mut self) -> Vec<Token> {
let mut tokens = Vec::new();
let mut bol = true;
let mut space = false;
loop {
let (saw_newline, saw_space) = self.skip_whitespace();
bol |= saw_newline;
space |= saw_space || saw_newline;
if self.eof() {
let end = self.bytes.len();
tokens.push(Token {
kind: TokenKind::Eof,
range: self.range(end, end),
bol,
preceded_by_space: space,
errors: std::mem::take(&mut self.pending),
});
break;
}
let start = self.pos;
let kind = self.scan_token();
tokens.push(Token {
kind,
range: self.range(start, self.pos),
bol,
preceded_by_space: space,
errors: std::mem::take(&mut self.pending),
});
bol = false;
space = false;
}
tokens
}
fn skip_whitespace(&mut self) -> (bool, bool) {
let mut newline = false;
let mut space = false;
loop {
match self.peek() {
Some(b'\n') => {
self.pos += 1;
newline = true;
}
Some(b' ' | b'\t' | b'\r' | 0x0b | 0x0c) => {
self.pos += 1;
space = true;
}
Some(b'\\' | b'?') if self.is_line_splice(self.pos) => {
self.pos += self.line_splice_len(self.pos);
space = true;
}
Some(b'/') if self.peek_at(1) == Some(b'*') => {
let start = self.pos;
self.pos += 2;
let mut closed = false;
while let Some(c) = self.peek() {
if c == b'*' && self.peek_at(1) == Some(b'/') {
self.pos += 2;
closed = true;
break;
}
self.pos += 1;
}
if !closed {
let range = self.range(start, self.bytes.len());
self.lexical_error(range, "unterminated comment");
}
space = true;
}
Some(b'/') if self.peek_at(1) == Some(b'/') => {
let start = self.pos;
self.pos += 2;
while let Some(c) = self.peek() {
if c == b'\n' {
break;
}
if matches!(c, b'\\' | b'?') && self.is_line_splice(self.pos) {
self.pos += self.line_splice_len(self.pos);
continue;
}
self.pos += 1;
}
if let Some(message) = self
.options
.gating
.requires("a '//' comment", Standard::C99)
{
let range = self.range(start, self.pos);
self.lexical_error(range, message);
}
space = true;
}
_ => return (newline, space),
}
}
}
fn is_line_splice(&self, at: usize) -> bool {
self.line_splice_len(at) > 0
}
fn line_splice_len(&self, at: usize) -> usize {
let lead = match self.trigraph_at(at) {
Some(b'\\') => 3,
Some(_) => return 0,
None if self.bytes.get(at) == Some(&b'\\') => 1,
None => return 0,
};
match (self.bytes.get(at + lead), self.bytes.get(at + lead + 1)) {
(Some(b'\n'), _) => lead + 1,
(Some(b'\r'), Some(b'\n')) => lead + 2,
_ => 0,
}
}
fn trigraph_at(&self, at: usize) -> Option<u8> {
if !self.options.trigraphs
|| self.bytes.get(at) != Some(&b'?')
|| self.bytes.get(at + 1) != Some(&b'?')
{
return None;
}
let third = *self.bytes.get(at + 2)?;
TRIGRAPHS
.iter()
.find(|(c, _)| *c == third)
.map(|(_, replacement)| *replacement)
}
fn trigraph_len(&self, at: usize) -> usize {
if self.trigraph_at(at).is_some() { 3 } else { 1 }
}
fn literal_prefix(&self, first: u8) -> Option<(StrKind, usize)> {
let (kind, len) = match first {
b'L' => (StrKind::Wide, 1),
b'U' => (StrKind::Utf32, 1),
b'u' if self.peek_at(1) == Some(b'8') => (StrKind::Utf8, 2),
b'u' => (StrKind::Utf16, 1),
_ => return None,
};
matches!(self.peek_at(len), Some(b'"' | b'\'')).then_some((kind, len))
}
fn extended_ident_start(&self) -> bool {
match self.peek() {
Some(b'\\') if matches!(self.peek_at(1), Some(b'u' | b'U')) => {
let want = if self.peek_at(1) == Some(b'u') { 4 } else { 8 };
(0..want).all(|i| self.peek_at(2 + i).is_some_and(|c| c.is_ascii_hexdigit()))
}
Some(c) if c >= 0x80 => self.text[self.pos..]
.chars()
.next()
.is_some_and(|ch| is_extended_ident_char(ch, true)),
_ => false,
}
}
fn scan_token(&mut self) -> TokenKind {
let Some(c) = self.peek() else {
return TokenKind::Eof;
};
if is_ident_start(c, self.options.dollar_in_identifiers) {
if let Some((kind, len)) = self.literal_prefix(c) {
let start = self.pos;
self.pos += len;
let character = self.peek() == Some(b'\'');
if let Some(message) = self.options.gating.requires(
&format!("a '{}' literal", kind.prefix()),
kind.since(character),
) {
let range = self.range(start, self.pos);
self.error(range, message);
}
return if character {
self.scan_char_constant(kind)
} else {
self.scan_string_literal(kind)
};
}
return self.scan_ident();
}
if self.extended_ident_start() {
return self.scan_ident();
}
if c.is_ascii_digit() || (c == b'.' && self.peek_at(1).is_some_and(|d| d.is_ascii_digit()))
{
return self.scan_number();
}
if c == b'\'' {
return self.scan_char_constant(StrKind::Narrow);
}
if c == b'"' {
return self.scan_string_literal(StrKind::Narrow);
}
if let Some(p) = self.scan_punctuator() {
return TokenKind::Punct(p);
}
let start = self.pos;
let ch = match self.trigraph_at(start) {
Some(c) => {
self.pos += 3;
c as char
}
None => {
let ch = self.text[start..].chars().next().unwrap_or('\u{fffd}');
self.pos += ch.len_utf8();
ch
}
};
let range = self.range(start, self.pos);
self.error(
range,
format!("unexpected character '{}' in program", ch.escape_debug()),
);
TokenKind::Error(ch.to_string())
}
fn scan_ident(&mut self) -> TokenKind {
let start = self.pos;
let mut spliced: Option<String> = None;
let mut segment = start;
let mut extended = false;
loop {
match self.peek() {
Some(c) if c < 0x80 && is_ident_continue(c, self.options.dollar_in_identifiers) => {
self.pos += 1;
}
Some(b'\\' | b'?')
if self.line_splice_len(self.pos) > 0
&& self
.bytes
.get(self.pos + self.line_splice_len(self.pos))
.is_some_and(|c| {
is_ident_continue(*c, self.options.dollar_in_identifiers)
}) =>
{
let text = spliced.get_or_insert_with(String::new);
text.push_str(&self.text[segment..self.pos]);
self.pos += self.line_splice_len(self.pos);
segment = self.pos;
}
Some(b'\\') if matches!(self.peek_at(1), Some(b'u' | b'U')) => {
let at = self.pos;
let Some(ch) = self.scan_ident_ucn(at == start) else {
break;
};
let text = spliced.get_or_insert_with(String::new);
text.push_str(&self.text[segment..at]);
text.push(ch);
segment = self.pos;
extended = true;
}
Some(c) if c >= 0x80 => {
let ch = self.text[self.pos..].chars().next().unwrap_or('\u{fffd}');
if !is_extended_ident_char(ch, self.pos == start) {
break;
}
self.pos += ch.len_utf8();
extended = true;
}
_ => break,
}
}
let text = match spliced {
Some(mut text) => {
text.push_str(&self.text[segment..self.pos]);
text
}
None => self.text[start..self.pos].to_owned(),
};
if text.is_empty() {
if self.pos == start {
let ch = self.text[start..].chars().next().unwrap_or('\u{fffd}');
self.pos += ch.len_utf8();
}
return TokenKind::Error(self.text[start..self.pos].to_owned());
}
if extended && !unicode_normalization::is_nfc(&text) {
let range = self.range(start, self.pos);
self.error(
range,
format!(
"identifier '{text}' is not in Unicode Normalization Form C; \
write the composed form"
),
);
}
match Keyword::from_str(&text, self.options.standard) {
Some(k) => TokenKind::Keyword(k),
None => TokenKind::Ident(text),
}
}
fn scan_ident_ucn(&mut self, start: bool) -> Option<char> {
let at = self.pos;
let want = if self.peek_at(1) == Some(b'u') { 4 } else { 8 };
let mut value: u32 = 0;
for i in 0..want {
let digit = self.peek_at(2 + i).and_then(|c| (c as char).to_digit(16))?;
value = value * 16 + digit;
}
let spelling = if want == 4 { 'u' } else { 'U' };
let ch = char::from_u32(value);
if !ch.is_some_and(|ch| is_extended_ident_char(ch, start)) {
self.pos += 2 + want;
let range = self.range(at, self.pos);
let digits = if want == 4 {
format!("{value:04X}")
} else {
format!("{value:08X}")
};
let message =
format!("'\\{spelling}{digits}' is not a valid character in an identifier");
if value < 0xA0 || (0xD800..=0xDFFF).contains(&value) {
self.lexical_error(range, message);
} else {
self.error(range, message);
}
return None;
}
if let Some(message) = self
.options
.gating
.requires("a universal character name", Standard::C99)
{
let range = self.range(at, at + 2 + want);
self.error(range, message);
}
self.pos += 2 + want;
ch
}
fn scan_punctuator(&mut self) -> Option<Punct> {
if self.trigraph_at(self.pos).is_some() {
return self.scan_trigraph_punctuator();
}
let rest = &self.text[self.pos..];
for (spelling, punct) in PUNCTUATORS {
if rest.starts_with(spelling) {
self.pos += spelling.len();
return Some(*punct);
}
}
None
}
fn scan_trigraph_punctuator(&mut self) -> Option<Punct> {
const LONGEST: usize = 4;
let mut logical = [0u8; LONGEST];
let mut widths = [0usize; LONGEST];
let mut count = 0;
let mut at = self.pos;
while count < LONGEST {
let (c, width) = match self.trigraph_at(at) {
Some(c) => (c, 3),
None => match self.bytes.get(at) {
Some(c) => (*c, 1),
None => break,
},
};
if c == b'\\' || !c.is_ascii() {
break;
}
logical[count] = c;
widths[count] = width;
count += 1;
at += width;
}
let text = std::str::from_utf8(&logical[..count]).ok()?;
for (spelling, punct) in PUNCTUATORS {
if text.starts_with(spelling) {
self.pos += widths[..spelling.len()].iter().sum::<usize>();
return Some(*punct);
}
}
None
}
fn scan_number(&mut self) -> TokenKind {
let start = self.pos;
if self.peek() == Some(b'.') {
self.pos += 1;
}
self.pos += 1;
let mut separators = false;
while let Some(c) = self.peek() {
if matches!(c, b'e' | b'E' | b'p' | b'P')
&& matches!(self.peek_at(1), Some(b'+') | Some(b'-'))
{
self.pos += 2;
continue;
}
if c == b'\''
&& self
.peek_at(1)
.is_some_and(|d| d.is_ascii_alphanumeric() || d == b'_')
{
separators = true;
self.pos += 1;
continue;
}
if c.is_ascii_alphanumeric()
|| c == b'_'
|| c == b'.'
|| (c == b'$' && self.options.dollar_in_identifiers)
{
self.pos += 1;
continue;
}
break;
}
let text = &self.text[start..self.pos];
let range = self.range(start, self.pos);
if separators
&& let Some(message) = self
.options
.gating
.requires("a digit separator", Standard::C23)
{
self.error(range, message);
}
let stripped: String;
let digits = if separators {
stripped = text.replace('\'', "");
stripped.as_str()
} else {
text
};
let lower = digits.to_ascii_lowercase();
let hex = lower.starts_with("0x");
let is_float = if hex {
digits.contains('.') || lower[2..].contains('p')
} else {
digits.contains('.') || (!lower.starts_with("0b") && lower.contains('e'))
};
if is_float {
TokenKind::Float(self.decode_float(digits, text, range, hex))
} else {
TokenKind::Int(self.decode_int(digits, text, range))
}
}
fn decode_int(&mut self, digits: &str, text: &str, range: SourceRange) -> IntLit {
let bytes = digits.as_bytes();
let (base, digits_start) =
if digits.len() >= 2 && (bytes[1] | 0x20) == b'x' && bytes[0] == b'0' {
(NumBase::Hex, 2)
} else if digits.len() >= 2 && (bytes[1] | 0x20) == b'b' && bytes[0] == b'0' {
(NumBase::Binary, 2)
} else if bytes[0] == b'0' && digits.len() > 1 {
(NumBase::Octal, 1)
} else {
(NumBase::Decimal, 0)
};
if base == NumBase::Binary
&& let Some(message) = self
.options
.gating
.requires("a binary integer constant", Standard::C23)
{
self.error(range, message);
}
let radix = base.radix();
let scan_radix = if radix < 10 { 10 } else { radix };
let mut i = digits_start;
let mut value: u128 = 0;
let mut overflow = false;
let mut bad_digit: Option<char> = None;
while i < bytes.len() {
let c = bytes[i] as char;
let digit = match c.to_digit(scan_radix) {
Some(d) => d,
None => break,
};
if digit >= radix && bad_digit.is_none() {
bad_digit = Some(c);
}
match value
.checked_mul(radix as u128)
.and_then(|v| v.checked_add(digit as u128))
{
Some(v) => value = v,
None => overflow = true,
}
i += 1;
}
if i == digits_start && matches!(base, NumBase::Hex | NumBase::Binary) {
let prefix = &digits[..2];
self.error(
range,
format!(
"expected digits after '{prefix}' in {} constant",
base.as_str()
),
);
}
if let Some(c) = bad_digit {
self.error(
range,
format!("invalid digit '{c}' in {} constant '{text}'", base.as_str()),
);
}
if overflow {
self.error(range, format!("integer constant '{text}' is too large"));
}
let suffix = &digits[i..];
let (unsigned, long) = match parse_int_suffix(suffix) {
Some(v) => v,
None => {
if matches!(suffix, "i" | "j" | "I" | "J") {
let digits = text.strip_suffix(suffix).unwrap_or(text);
self.error(
range,
format!(
"'{text}' is a complex integer constant, which is a GNU extension \
cinrs does not support; write '{digits}.0{suffix}' for the \
complex floating constant"
),
);
} else {
self.error(
range,
format!("invalid suffix '{suffix}' on integer constant '{text}'"),
);
}
(false, LongKind::None)
}
};
IntLit {
value,
base,
unsigned,
long,
text: text.to_owned(),
}
}
fn decode_float(
&mut self,
digits: &str,
text: &str,
range: SourceRange,
hex: bool,
) -> FloatLit {
let (body, suffix) = split_float_suffix(digits, hex);
let (suffix_kind, imaginary) = self.float_suffix(suffix, text, range);
if hex
&& let Some(message) = self
.options
.gating
.requires("a hexadecimal floating constant", Standard::C99)
{
self.error(range, message);
}
let value = if hex {
match parse_hex_float(body) {
Some(v) => v,
None => {
self.error(
range,
format!(
"invalid hexadecimal floating constant '{text}'; \
a 'p' exponent is required"
),
);
0.0
}
}
} else {
match parse_decimal_float(body) {
Some(v) => v,
None => {
self.error(range, format!("invalid floating constant '{text}'"));
0.0
}
}
};
FloatLit {
value,
suffix: suffix_kind,
imaginary,
hex,
text: text.to_owned(),
}
}
fn float_suffix(
&mut self,
suffix: &str,
text: &str,
range: SourceRange,
) -> (FloatSuffix, bool) {
let lower = suffix.to_ascii_lowercase();
match lower.as_str() {
"" => return (FloatSuffix::None, false),
"f" => return (FloatSuffix::Float, false),
"l" => return (FloatSuffix::LongDouble, false),
_ => {}
}
let imaginary = match lower.as_str() {
"i" | "j" => Some(FloatSuffix::None),
"if" | "fi" | "jf" | "fj" => Some(FloatSuffix::Float),
"il" | "li" | "jl" | "lj" => Some(FloatSuffix::LongDouble),
_ => None,
};
if let Some(kind) = imaginary {
if !self.options.complex {
self.error(
range,
format!(
"invalid suffix '{suffix}' on floating constant '{text}': an \
imaginary constant needs _Complex. {}",
crate::COMPLEX_UNSUPPORTED
),
);
return (FloatSuffix::None, false);
}
if let Some(message) = self
.options
.gating
.requires("an imaginary constant", Standard::C99)
{
self.error(range, message);
return (FloatSuffix::None, false);
}
return (kind, true);
}
let refusal = match lower.as_str() {
"df" | "dd" | "dl" => Some(
"the decimal floating types (_Decimal32, _Decimal64, _Decimal128) are not \
supported: they are radix-10 and no Rust type is",
),
"f16" | "f16x" | "bf16" => Some(
"'_Float16' is not supported: Rust's `f16` is unstable, and rounding the \
constant to a wider type would change what the program computes",
),
_ => None,
};
if let Some(reason) = refusal {
self.error(
range,
format!("invalid suffix '{suffix}' on floating constant '{text}': {reason}"),
);
return (FloatSuffix::None, false);
}
let wider = matches!(
lower.as_str(),
"d" | "w" | "q" | "f64" | "f64x" | "f32x" | "f128" | "f128x"
);
if !wider && lower != "f32" {
self.error(
range,
format!("invalid suffix '{suffix}' on floating constant '{text}'"),
);
return (FloatSuffix::None, false);
}
if !self.options.gating.dialect.is_gnu() {
let gnu = self.options.gating.standard.macro_name_in(Dialect::Gnu);
let here = self
.options
.gating
.standard
.macro_name_in(self.options.gating.dialect);
self.error(
range,
format!(
"the suffix '{suffix}' on a floating constant is a GNU extension, and \
requires a GNU dialect ({gnu}) (this block is {here})"
),
);
return (FloatSuffix::None, false);
}
if lower == "f32" {
return (FloatSuffix::Float, false);
}
(FloatSuffix::LongDouble, false)
}
fn scan_char_constant(&mut self, kind: StrKind) -> TokenKind {
let start = self.pos;
debug_assert_eq!(self.peek(), Some(b'\''));
self.pos += 1;
let mut values: Vec<u32> = Vec::new();
let mut terminated = false;
let mut characters = 0usize;
while let Some(c) = self.peek() {
if c == b'\'' {
self.pos += 1;
terminated = true;
break;
}
if c == b'\n' {
break;
}
let before = values.len();
self.read_char_element(kind, &mut values);
if values.len() > before {
characters += 1;
}
}
let range = self.range(start, self.pos);
if !terminated {
self.error(range, "missing terminating \' character");
}
if values.is_empty() {
self.error(range, "empty character constant");
}
if values.len() > 1 {
match kind {
StrKind::Narrow | StrKind::Wide => {
self.warning(range, "multi-character character constant");
}
_ if characters > 1 => {
self.error(
range,
format!(
"a '{}' character constant holds exactly one character",
kind.prefix()
),
);
}
_ => {
self.error(
range,
format!(
"the character in a '{}' character constant must fit in a \
single code unit",
kind.prefix()
),
);
}
}
}
let value = if kind != StrKind::Narrow {
values.last().copied().unwrap_or(0) as i64
} else if values.len() <= 1 {
values.first().copied().unwrap_or(0) as i64
} else {
let mut v: u32 = 0;
for b in &values {
v = (v << 8) | (*b & 0xff);
}
v as i32 as i64
};
TokenKind::Char(CharLit {
value,
kind,
text: self.text[start..self.pos].to_owned(),
})
}
fn scan_string_literal(&mut self, kind: StrKind) -> TokenKind {
let start = self.pos;
debug_assert_eq!(self.peek(), Some(b'"'));
self.pos += 1;
let mut values: Vec<u32> = Vec::new();
let mut terminated = false;
while let Some(c) = self.peek() {
if c == b'"' {
self.pos += 1;
terminated = true;
break;
}
if c == b'\n' {
break;
}
self.read_char_element(kind, &mut values);
}
let range = self.range(start, self.pos);
if !terminated {
self.error(range, "missing terminating \" character");
}
TokenKind::Str(StrLit {
kind,
values,
text: self.text[start..self.pos].to_owned(),
})
}
fn read_char_element(&mut self, kind: StrKind, out: &mut Vec<u32>) {
if self.is_line_splice(self.pos) {
self.pos += self.line_splice_len(self.pos);
return;
}
let start = self.pos;
let trigraph = self.trigraph_at(self.pos);
if trigraph != Some(b'\\') && self.peek() != Some(b'\\') {
match trigraph {
Some(c) => {
self.pos += 3;
out.push(u32::from(c));
}
None if kind.is_bytes() => {
self.pos += 1;
out.push(self.bytes[start] as u32);
}
None => {
let ch = self.text[start..].chars().next().unwrap_or('\u{fffd}');
self.pos += ch.len_utf8();
push_character(ch as u32, kind, self.options.wchar_bits, out);
}
}
return;
}
self.pos += self.trigraph_len(self.pos);
let e = match self.trigraph_at(self.pos) {
Some(c) => c,
None => match self.peek() {
Some(c) => c,
None => {
let range = self.range(start, self.pos);
self.error(range, "incomplete escape sequence");
return;
}
},
};
self.pos += self.trigraph_len(self.pos);
let simple = match e {
b'\'' => Some(0x27),
b'"' => Some(0x22),
b'?' => Some(0x3f),
b'\\' => Some(0x5c),
b'a' => Some(0x07),
b'b' => Some(0x08),
b'e' => Some(0x1b),
b'f' => Some(0x0c),
b'n' => Some(0x0a),
b'r' => Some(0x0d),
b't' => Some(0x09),
b'v' => Some(0x0b),
_ => None,
};
if let Some(v) = simple {
out.push(v);
return;
}
match e {
b'0'..=b'7' => {
let mut v: u32 = (e - b'0') as u32;
for _ in 0..2 {
match self.peek() {
Some(d @ b'0'..=b'7') => {
v = v * 8 + (d - b'0') as u32;
self.pos += 1;
}
_ => break,
}
}
self.push_escape_value(v, kind, start, out);
}
b'x' => {
let mut v: u32 = 0;
let mut any = false;
let mut overflow = false;
while let Some(d) = self.peek().and_then(|c| (c as char).to_digit(16)) {
any = true;
v = match v.checked_mul(16).and_then(|v| v.checked_add(d)) {
Some(v) => v,
None => {
overflow = true;
v
}
};
self.pos += 1;
}
let range = self.range(start, self.pos);
if !any {
self.error(range, "'\\x' used with no following hex digits");
} else if overflow {
self.error(range, "hex escape sequence out of range");
}
self.push_escape_value(v, kind, start, out);
}
b'u' | b'U' => {
if let Some(message) = self
.options
.gating
.requires("a universal character name", Standard::C99)
{
let range = self.range(start, self.pos);
self.error(range, message);
}
let want = if e == b'u' { 4 } else { 8 };
let mut v: u32 = 0;
let mut count = 0;
while count < want {
match self.peek().and_then(|c| (c as char).to_digit(16)) {
Some(d) => {
v = v.wrapping_mul(16).wrapping_add(d);
self.pos += 1;
count += 1;
}
None => break,
}
}
let range = self.range(start, self.pos);
if count != want {
self.error(
range,
format!("incomplete universal character name; expected {want} hex digits"),
);
return;
}
match char::from_u32(v) {
Some(ch) if kind.is_bytes() => {
let mut buf = [0u8; 4];
for b in ch.encode_utf8(&mut buf).as_bytes() {
out.push(*b as u32);
}
}
Some(ch) => push_character(ch as u32, kind, self.options.wchar_bits, out),
None => {
self.lexical_error(
range,
format!("'\\u{v:04X}' is not a valid universal character name"),
);
}
}
}
_ => {
let range = self.range(start, self.pos);
self.error(
range,
format!("unknown escape sequence '\\{}'", (e as char).escape_debug()),
);
out.push(e as u32);
}
}
}
fn push_escape_value(&mut self, v: u32, kind: StrKind, start: usize, out: &mut Vec<u32>) {
let max = kind.max_element(self.options.wchar_bits);
if v > max {
let range = self.range(start, self.pos);
let ty = match kind {
StrKind::Narrow => "char",
StrKind::Utf8 => "char8_t",
StrKind::Utf16 => "char16_t",
StrKind::Utf32 => "char32_t",
StrKind::Wide => "wchar_t",
};
self.error(
range,
format!("escape sequence out of range for type '{ty}'"),
);
out.push(v & max);
} else {
out.push(v);
}
}
}
fn push_character(value: u32, kind: StrKind, wchar_bits: u32, out: &mut Vec<u32>) {
if !kind.is_utf16(wchar_bits) || value <= 0xffff {
out.push(value);
return;
}
let v = value - 0x1_0000;
out.push(0xd800 + (v >> 10));
out.push(0xdc00 + (v & 0x3ff));
}
fn is_ident_start(c: u8, dollar: bool) -> bool {
c.is_ascii_alphabetic() || c == b'_' || (dollar && c == b'$')
}
fn is_ident_continue(c: u8, dollar: bool) -> bool {
c.is_ascii_alphanumeric() || c == b'_' || (dollar && c == b'$')
}
fn is_extended_ident_char(ch: char, start: bool) -> bool {
if ch.is_ascii() {
return false;
}
if start {
unicode_ident::is_xid_start(ch)
} else {
unicode_ident::is_xid_continue(ch)
}
}
fn parse_int_suffix(s: &str) -> Option<(bool, LongKind)> {
if s.is_empty() {
return Some((false, LongKind::None));
}
let b = s.as_bytes();
let mut i = 0;
let mut unsigned = false;
let mut long = LongKind::None;
if b[i] == b'u' || b[i] == b'U' {
unsigned = true;
i += 1;
}
if i < b.len() && (b[i] == b'l' || b[i] == b'L') {
if i + 1 < b.len() && b[i + 1] == b[i] {
long = LongKind::LongLong;
i += 2;
} else {
long = LongKind::Long;
i += 1;
}
}
if !unsigned && i < b.len() && (b[i] == b'u' || b[i] == b'U') {
unsigned = true;
i += 1;
}
(i == b.len()).then_some((unsigned, long))
}
fn split_float_suffix(text: &str, hex: bool) -> (&str, &str) {
let b = text.as_bytes();
let mut i = 0;
let (exponent, digit): (u8, fn(u8) -> bool) = if hex {
i = 2; (b'p', |c| c.is_ascii_hexdigit())
} else {
(b'e', |c| c.is_ascii_digit())
};
while i < b.len() && (digit(b[i]) || b[i] == b'.') {
i += 1;
}
if i < b.len() && b[i] | 0x20 == exponent {
i += 1;
if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
i += 1;
}
while i < b.len() && b[i].is_ascii_digit() {
i += 1;
}
}
(&text[..i], &text[i..])
}
fn parse_decimal_float(body: &str) -> Option<f64> {
if body.is_empty() {
return None;
}
let (mantissa, exponent) = match body.find(['e', 'E']) {
Some(i) => (&body[..i], Some(&body[i + 1..])),
None => (body, None),
};
let (int_part, frac_part) = match mantissa.find('.') {
Some(i) => (&mantissa[..i], &mantissa[i + 1..]),
None => (mantissa, ""),
};
if int_part.is_empty() && frac_part.is_empty() {
return None;
}
if !int_part.bytes().all(|c| c.is_ascii_digit())
|| !frac_part.bytes().all(|c| c.is_ascii_digit())
{
return None;
}
let exponent = match exponent {
None => 0i32,
Some(exponent) => {
let (sign, digits) = match exponent.as_bytes().first() {
Some(b'+') => (1, &exponent[1..]),
Some(b'-') => (-1, &exponent[1..]),
_ => (1, exponent),
};
if digits.is_empty() || !digits.bytes().all(|c| c.is_ascii_digit()) {
return None;
}
sign * digits.parse::<i32>().unwrap_or(i32::MAX / 2)
}
};
let normalized = format!(
"{}.{}e{}",
if int_part.is_empty() { "0" } else { int_part },
if frac_part.is_empty() { "0" } else { frac_part },
exponent
);
normalized.parse::<f64>().ok()
}
fn parse_hex_float(body: &str) -> Option<f64> {
let rest = body
.strip_prefix("0x")
.or_else(|| body.strip_prefix("0X"))?;
let p = rest.find(['p', 'P'])?;
let (mantissa, exponent) = (&rest[..p], &rest[p + 1..]);
let (int_part, frac_part) = match mantissa.find('.') {
Some(i) => (&mantissa[..i], &mantissa[i + 1..]),
None => (mantissa, ""),
};
if int_part.is_empty() && frac_part.is_empty() {
return None;
}
let mut value = 0f64;
for c in int_part.chars() {
value = value * 16.0 + c.to_digit(16)? as f64;
}
let mut scale = 1.0 / 16.0;
for c in frac_part.chars() {
value += c.to_digit(16)? as f64 * scale;
scale /= 16.0;
}
let (sign, digits) = match exponent.as_bytes().first() {
Some(b'+') => (1i32, &exponent[1..]),
Some(b'-') => (-1i32, &exponent[1..]),
_ => (1i32, exponent),
};
if digits.is_empty() || !digits.bytes().all(|c| c.is_ascii_digit()) {
return None;
}
let exp = sign * digits.parse::<i32>().unwrap_or(i32::MAX / 2);
Some(value * 2f64.powi(exp))
}