use rucc_base::{Interner, Symbol};
use rucc_diag::{Diagnostic, Span};
use rucc_session::Std;
use rucc_target::TargetInfo;
use crate::keyword::{Keyword, Keywords};
use crate::literal::{CharConstant, LiteralError, StringLiteral};
use crate::number::{FloatConstant, IntConstant, IntError};
use crate::remarks::Remarks;
use crate::token::{PpToken, PpTokenKind, Punct, TokenFlags};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TokenKind {
Keyword(Keyword),
Ident,
Int,
Float,
Char,
Str,
Punct(Punct),
Eof,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Token {
pub kind: TokenKind,
pub flags: TokenFlags,
pub value: u32,
pub span: Span,
}
impl Token {
#[inline]
#[must_use]
pub const fn is_eof(self) -> bool {
matches!(self.kind, TokenKind::Eof)
}
#[inline]
#[must_use]
pub const fn keyword(self) -> Option<Keyword> {
match self.kind {
TokenKind::Keyword(word) => Some(word),
_ => None,
}
}
#[inline]
#[must_use]
pub const fn punct(self) -> Option<Punct> {
match self.kind {
TokenKind::Punct(punct) => Some(punct),
_ => None,
}
}
#[inline]
#[must_use]
pub const fn ident(self) -> Option<Symbol> {
match self.kind {
TokenKind::Ident => Some(Symbol::from_raw(self.value)),
_ => None,
}
}
}
#[derive(Debug, Default)]
pub struct Tokens {
pub tokens: Vec<Token>,
pub ints: Vec<IntConstant>,
pub floats: Vec<FloatConstant>,
pub chars: Vec<CharConstant>,
pub strings: Vec<StringLiteral>,
}
impl Tokens {
#[must_use]
pub fn int(&self, token: Token) -> Option<&IntConstant> {
match token.kind {
TokenKind::Int => self.ints.get(token.value as usize),
_ => None,
}
}
#[must_use]
pub fn float(&self, token: Token) -> Option<&FloatConstant> {
match token.kind {
TokenKind::Float => self.floats.get(token.value as usize),
_ => None,
}
}
#[must_use]
pub fn character(&self, token: Token) -> Option<&CharConstant> {
match token.kind {
TokenKind::Char => self.chars.get(token.value as usize),
_ => None,
}
}
#[must_use]
pub fn string(&self, token: Token) -> Option<&StringLiteral> {
match token.kind {
TokenKind::Str => self.strings.get(token.value as usize),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Convert<'a> {
pub keywords: &'a Keywords,
pub interner: &'a Interner,
pub target: &'a TargetInfo,
pub std: Std,
pub pedantic: bool,
}
#[must_use]
pub fn convert(pp: &[PpToken], cx: &Convert<'_>) -> (Tokens, Vec<Diagnostic>) {
let mut out = Tokens { tokens: Vec::with_capacity(pp.len()), ..Tokens::default() };
let mut diagnostics = Vec::new();
let mut index = 0;
while index < pp.len() {
let token = pp[index];
index += 1;
match token.kind {
PpTokenKind::Ident => out.tokens.push(identifier(token, cx)),
PpTokenKind::Number => {
out.tokens.push(number(
token,
cx,
&mut out.ints,
&mut out.floats,
&mut diagnostics,
));
}
PpTokenKind::CharConst => {
out.tokens.push(char_const(token, cx, &mut out.chars, &mut diagnostics));
}
PpTokenKind::StringLit => {
let start = index - 1;
while pp.get(index).is_some_and(|next| next.kind == PpTokenKind::StringLit) {
index += 1;
}
let run = &pp[start..index];
out.tokens.push(string_lit(run, cx, &mut out.strings, &mut diagnostics));
}
PpTokenKind::Punct(punct) => out.tokens.push(Token {
kind: TokenKind::Punct(punct),
flags: token.flags,
value: 0,
span: token.span,
}),
PpTokenKind::Eof => out.tokens.push(Token {
kind: TokenKind::Eof,
flags: token.flags,
value: 0,
span: token.span,
}),
PpTokenKind::Other | PpTokenKind::HeaderName => {
let text = spelling(token, cx);
diagnostics
.push(Diagnostic::error(format!("stray '{text}' in program"), token.span));
}
}
}
if out.tokens.last().is_none_or(|last| !last.is_eof()) {
let end =
out.tokens.last().map_or(Span::new(0, 0), |last| Span::new(last.span.hi, last.span.hi));
out.tokens.push(Token {
kind: TokenKind::Eof,
flags: TokenFlags::EMPTY,
value: 0,
span: end,
});
}
(out, diagnostics)
}
fn spelling<'a>(token: PpToken, cx: &Convert<'a>) -> &'a str {
token.value.map_or("", |symbol| cx.interner.resolve(symbol))
}
fn identifier(token: PpToken, cx: &Convert<'_>) -> Token {
let symbol = token.value.expect("an identifier carries its spelling");
let kind = match cx.keywords.get(symbol) {
Some(word) => TokenKind::Keyword(word),
None => TokenKind::Ident,
};
Token { kind, flags: token.flags, value: symbol.raw(), span: token.span }
}
fn number(
token: PpToken,
cx: &Convert<'_>,
ints: &mut Vec<IntConstant>,
floats: &mut Vec<FloatConstant>,
diagnostics: &mut Vec<Diagnostic>,
) -> Token {
let text = spelling(token, cx);
match crate::number::integer(text, cx.std, cx.target) {
Ok(value) => {
report(value.remarks, None, token.span, cx, diagnostics);
ints.push(value);
let index = u32::try_from(ints.len() - 1).expect("that many constants in one file");
Token { kind: TokenKind::Int, flags: token.flags, value: index, span: token.span }
}
Err(IntError::Floating) => match crate::number::floating(text, cx.std, cx.target) {
Ok(value) => {
report(value.remarks, Some(value.ty.name()), token.span, cx, diagnostics);
floats.push(value);
let index =
u32::try_from(floats.len() - 1).expect("that many constants in one file");
Token { kind: TokenKind::Float, flags: token.flags, value: index, span: token.span }
}
Err(error) => {
diagnostics.push(Diagnostic::error(error.message(), token.span));
floats.push(zero_float(cx));
let index =
u32::try_from(floats.len() - 1).expect("that many constants in one file");
Token { kind: TokenKind::Float, flags: token.flags, value: index, span: token.span }
}
},
Err(error) => {
diagnostics.push(Diagnostic::error(error.message(), token.span));
ints.push(IntConstant {
value: 0,
ty: crate::number::IntConstantType::Standard(rucc_types::IntKind::Int),
remarks: Remarks::NONE,
});
let index = u32::try_from(ints.len() - 1).expect("that many constants in one file");
Token { kind: TokenKind::Int, flags: token.flags, value: index, span: token.span }
}
}
}
fn zero_float(cx: &Convert<'_>) -> FloatConstant {
let ty = crate::number::FloatConstantType::Double;
FloatConstant {
value: rucc_base::float::Float::zero(ty.format(cx.target), false),
ty,
imaginary: false,
remarks: Remarks::NONE,
}
}
fn char_const(
token: PpToken,
cx: &Convert<'_>,
chars: &mut Vec<CharConstant>,
diagnostics: &mut Vec<Diagnostic>,
) -> Token {
let text = spelling(token, cx);
let value = match crate::literal::character(text, cx.std, cx.target) {
Ok(value) => {
report(value.remarks, None, token.span, cx, diagnostics);
value
}
Err(error) => {
diagnostics.push(Diagnostic::error(error.message(), token.span));
CharConstant {
value: 0,
encoding: crate::literal::Encoding::Plain,
remarks: Remarks::NONE,
}
}
};
chars.push(value);
let index = u32::try_from(chars.len() - 1).expect("that many constants in one file");
Token { kind: TokenKind::Char, flags: token.flags, value: index, span: token.span }
}
fn string_lit(
run: &[PpToken],
cx: &Convert<'_>,
strings: &mut Vec<StringLiteral>,
diagnostics: &mut Vec<Diagnostic>,
) -> Token {
let first = run[0];
let span = first.span.to(run[run.len() - 1].span);
let texts: Vec<&str> = run.iter().map(|token| spelling(*token, cx)).collect();
let value = match crate::literal::strings(&texts, cx.std, cx.target) {
Ok(value) => {
report(value.remarks, None, span, cx, diagnostics);
value
}
Err(error) => {
diagnostics.push(Diagnostic::error(error.message(), span));
let encoding = if error == LiteralError::MixedEncodings {
crate::literal::Encoding::Plain
} else {
crate::literal::Encoding::read_prefix(texts[0])
};
StringLiteral { elements: Vec::new(), encoding, remarks: Remarks::NONE }
}
};
strings.push(value);
let index = u32::try_from(strings.len() - 1).expect("that many literals in one file");
Token { kind: TokenKind::Str, flags: first.flags, value: index, span }
}
fn report(
remarks: Remarks,
type_name: Option<&str>,
span: Span,
cx: &Convert<'_>,
diagnostics: &mut Vec<Diagnostic>,
) {
if remarks.is_none() {
return;
}
let always: [(Remarks, &str); 6] = [
(Remarks::MULTICHARACTER, "multi-character character constant"),
(Remarks::TOO_LONG, "character constant too long for its type"),
(Remarks::UNKNOWN_ESCAPE, "unknown escape sequence"),
(Remarks::HEX_ESCAPE_OUT_OF_RANGE, "hex escape sequence out of range"),
(Remarks::OCTAL_ESCAPE_OUT_OF_RANGE, "octal escape sequence out of range"),
(Remarks::UNSIGNED, "integer constant is so large that it is unsigned"),
];
for (remark, message) in always {
if remarks.has(remark) {
diagnostics.push(Diagnostic::warning(message, span));
}
}
if remarks.has(Remarks::OUT_OF_RANGE) {
let ty = type_name.unwrap_or("double");
diagnostics
.push(Diagnostic::warning(format!("floating constant exceeds range of '{ty}'"), span));
}
if remarks.has(Remarks::TRUNCATED) {
diagnostics.push(Diagnostic::warning("floating constant truncated to zero", span));
}
if !cx.pedantic {
return;
}
let pedantic: [(Remarks, &str); 9] = [
(Remarks::NON_ISO_ESCAPE, "non-ISO-standard escape sequence"),
(Remarks::DOUBLE_SUFFIX, "suffix for double constant is a GCC extension"),
(Remarks::IMAGINARY, "imaginary constants are a GCC extension"),
(Remarks::BINARY, "binary constants are a C23 feature or GCC extension"),
(Remarks::EXTENDED_SUFFIX, "non-standard suffix on floating constant"),
(Remarks::HEX_FLOAT, "use of C99 hexadecimal floating constant"),
(Remarks::LONG_LONG, "use of C99 long long integer constant"),
(Remarks::SEPARATORS, "digit separators are a C23 feature"),
(Remarks::BIT_INT, "'_BitInt' constants are a C23 feature"),
];
for (remark, message) in pedantic {
if remarks.has(remark) {
diagnostics.push(Diagnostic::warning(message, span));
}
}
if remarks.has(Remarks::UCN) {
diagnostics.push(Diagnostic::warning(
"universal character names are only valid in C++ and C99",
span,
));
}
}
#[cfg(test)]
mod tests {
use rucc_target::Triple;
use super::*;
use crate::lexer::{Options, tokenize};
struct Fixture {
interner: Interner,
keywords: Keywords,
target: TargetInfo,
std: Std,
pedantic: bool,
}
impl Fixture {
fn new(std: Std) -> Fixture {
let mut interner = Interner::new();
let keywords = Keywords::new(&mut interner, std, true);
let target =
TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().expect("a triple"));
Fixture { interner, keywords, target, std, pedantic: false }
}
fn run(&mut self, src: &str) -> (Tokens, Vec<String>) {
let (pp, lex_diagnostics) =
tokenize(src.as_bytes(), 0, Options::new(), &mut self.interner);
assert!(lex_diagnostics.is_empty(), "the scanner disliked the source: {src}");
let cx = Convert {
keywords: &self.keywords,
interner: &self.interner,
target: &self.target,
std: self.std,
pedantic: self.pedantic,
};
let (tokens, diagnostics) = convert(&pp, &cx);
(tokens, diagnostics.iter().map(|d| d.message.clone()).collect())
}
}
fn kinds(src: &str) -> Vec<TokenKind> {
Fixture::new(Std::C23).run(src).0.tokens.iter().map(|t| t.kind).collect()
}
#[test]
fn a_token_is_sixteen_bytes() {
assert_eq!(size_of::<Token>(), 16);
}
#[test]
fn a_declaration_converts_into_keywords_an_identifier_and_a_constant() {
assert_eq!(
kinds("int x = 1;"),
vec![
TokenKind::Keyword(Keyword::Int),
TokenKind::Ident,
TokenKind::Punct(Punct::Eq),
TokenKind::Int,
TokenKind::Punct(Punct::Semi),
TokenKind::Eof,
]
);
}
#[test]
fn the_dialect_decides_which_identifiers_are_keywords() {
let mut c89 = Fixture::new(Std::C89);
let (tokens, _) = c89.run("restrict");
assert_eq!(tokens.tokens[0].kind, TokenKind::Ident);
let mut c99 = Fixture::new(Std::C99);
let (tokens, _) = c99.run("restrict");
assert_eq!(tokens.tokens[0].kind, TokenKind::Keyword(Keyword::Restrict));
}
#[test]
fn a_number_becomes_whichever_kind_of_constant_it_is() {
let mut fixture = Fixture::new(Std::C23);
let (tokens, diagnostics) = fixture.run("1 2.5 0x1p3 1u");
assert!(diagnostics.is_empty());
let kinds: Vec<_> = tokens.tokens.iter().map(|t| t.kind).collect();
assert_eq!(
kinds,
vec![
TokenKind::Int,
TokenKind::Float,
TokenKind::Float,
TokenKind::Int,
TokenKind::Eof
]
);
assert_eq!(tokens.int(tokens.tokens[0]).expect("an integer").value, 1);
assert!(tokens.float(tokens.tokens[1]).is_some());
assert_eq!(tokens.tokens[2].value, 1);
assert_eq!(tokens.tokens[3].value, 1);
assert_eq!(tokens.int(tokens.tokens[3]).expect("an integer").value, 1);
assert!(tokens.float(tokens.tokens[0]).is_none());
assert!(tokens.string(tokens.tokens[0]).is_none());
}
#[test]
fn adjacent_string_literals_become_one_token() {
let mut fixture = Fixture::new(Std::C23);
let (tokens, diagnostics) = fixture.run(r#"char *s = "a" "b" L"c";"#);
assert!(diagnostics.is_empty(), "{diagnostics:?}");
let literal = tokens
.tokens
.iter()
.find(|t| t.kind == TokenKind::Str)
.copied()
.expect("a string literal");
let value = tokens.string(literal).expect("the literal");
assert_eq!(value.elements, vec![0x61, 0x62, 0x63]);
assert_eq!(value.encoding, crate::literal::Encoding::Wide);
assert_eq!(tokens.tokens.iter().filter(|t| t.kind == TokenKind::Str).count(), 1);
assert_eq!(literal.span.lo, 10);
assert_eq!(literal.span.hi, 22);
}
#[test]
fn a_character_constant_carries_its_value_and_its_warning() {
let mut fixture = Fixture::new(Std::C23);
let (tokens, diagnostics) = fixture.run("'ab'");
assert_eq!(diagnostics, vec!["multi-character character constant".to_owned()]);
assert_eq!(tokens.character(tokens.tokens[0]).expect("a constant").value, 0x6162);
}
#[test]
fn the_warnings_that_need_no_flag_are_given_without_one() {
let mut fixture = Fixture::new(Std::C17);
let (_, diagnostics) = fixture.run(r"'abcde' '\q' '\x1ff' '\400' 1e400 1e-400");
assert_eq!(
diagnostics,
vec![
"character constant too long for its type".to_owned(),
"unknown escape sequence".to_owned(),
"hex escape sequence out of range".to_owned(),
"octal escape sequence out of range".to_owned(),
"floating constant exceeds range of 'double'".to_owned(),
"floating constant truncated to zero".to_owned(),
]
);
}
#[test]
fn the_warnings_that_need_pedantic_wait_for_it() {
let mut quiet = Fixture::new(Std::C17);
let (_, diagnostics) = quiet.run(r"1.0d 1.0i 0b1010 '\e'");
assert!(diagnostics.is_empty(), "{diagnostics:?}");
let mut loud = Fixture::new(Std::C17);
loud.pedantic = true;
let (_, diagnostics) = loud.run(r"1.0d 1.0i 0b1010 '\e'");
assert_eq!(
diagnostics,
vec![
"suffix for double constant is a GCC extension".to_owned(),
"imaginary constants are a GCC extension".to_owned(),
"binary constants are a C23 feature or GCC extension".to_owned(),
"non-ISO-standard escape sequence".to_owned(),
]
);
}
#[test]
fn the_overflow_warning_names_the_type_the_constant_actually_has() {
let mut fixture = Fixture::new(Std::C23);
let (_, diagnostics) = fixture.run("1e400f");
assert_eq!(diagnostics, vec!["floating constant exceeds range of 'float'".to_owned()]);
}
#[test]
fn a_constant_that_will_not_convert_still_leaves_a_token_behind() {
let mut fixture = Fixture::new(Std::C23);
let (tokens, diagnostics) = fixture.run("int x = 1.2.3;");
assert_eq!(diagnostics.len(), 1);
let kinds: Vec<_> = tokens.tokens.iter().map(|t| t.kind).collect();
assert_eq!(
kinds,
vec![
TokenKind::Keyword(Keyword::Int),
TokenKind::Ident,
TokenKind::Punct(Punct::Eq),
TokenKind::Float,
TokenKind::Punct(Punct::Semi),
TokenKind::Eof,
]
);
let mut fixture = Fixture::new(Std::C23);
let (tokens, diagnostics) = fixture.run("int x = 42ux;");
assert_eq!(diagnostics, vec!["invalid suffix on integer constant".to_owned()]);
assert_eq!(tokens.int(tokens.tokens[3]).expect("a stand in").value, 0);
}
#[test]
fn a_run_of_literals_with_two_prefixes_is_refused_the_way_gcc_refuses_it() {
let mut fixture = Fixture::new(Std::C23);
let (tokens, diagnostics) = fixture.run(r#"u"a" L"b""#);
assert_eq!(
diagnostics,
vec!["unsupported non-standard concatenation of string literals".to_owned()]
);
assert!(tokens.string(tokens.tokens[0]).expect("a stand in").elements.is_empty());
}
#[test]
fn a_stray_byte_is_an_error_here_and_nowhere_earlier() {
let mut fixture = Fixture::new(Std::C23);
let (tokens, diagnostics) = fixture.run("a ` b");
assert_eq!(diagnostics, vec!["stray '`' in program".to_owned()]);
let kinds: Vec<_> = tokens.tokens.iter().map(|t| t.kind).collect();
assert_eq!(kinds, vec![TokenKind::Ident, TokenKind::Ident, TokenKind::Eof]);
}
#[test]
fn the_stream_always_ends_in_end_of_file() {
let mut fixture = Fixture::new(Std::C23);
let (tokens, _) = fixture.run("");
assert_eq!(tokens.tokens.len(), 1);
assert!(tokens.tokens[0].is_eof());
let (tokens, _) = convert(
&[],
&Convert {
keywords: &fixture.keywords,
interner: &fixture.interner,
target: &fixture.target,
std: fixture.std,
pedantic: false,
},
);
assert_eq!(tokens.tokens.len(), 1);
assert!(tokens.tokens[0].is_eof());
}
#[test]
fn a_token_says_what_it_is_without_the_caller_matching_on_the_kind() {
let mut fixture = Fixture::new(Std::C23);
let (tokens, _) = fixture.run("int x;");
assert_eq!(tokens.tokens[0].keyword(), Some(Keyword::Int));
assert_eq!(tokens.tokens[0].ident(), None);
assert!(tokens.tokens[1].ident().is_some());
assert_eq!(tokens.tokens[2].punct(), Some(Punct::Semi));
assert_eq!(tokens.tokens[2].keyword(), None);
}
#[test]
fn the_flags_come_through_from_the_preprocessing_token() {
let mut fixture = Fixture::new(Std::C23);
let (tokens, _) = fixture.run("a\n b");
assert!(tokens.tokens[0].flags.has(TokenFlags::START_OF_LINE));
assert!(tokens.tokens[1].flags.has(TokenFlags::START_OF_LINE));
assert!(tokens.tokens[1].flags.has(TokenFlags::LEADING_SPACE));
}
}