use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use crate::capture::{Pos, SourceRange};
use crate::diag::{Diagnostic, Diagnostics};
use crate::include;
use crate::lex::{
self, IntLit, Keyword, LexOptions, LongKind, NumBase, Punct, StrKind, StrLit, TokenKind,
};
use crate::target::{Env, Os, TargetModel, TargetSource};
use crate::{Dialect, Gating, Options, Standard};
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Expansion {
pub name: String,
pub invocation: SourceRange,
pub definition: SourceRange,
pub parent: Option<Arc<Expansion>>,
}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub enum Origin {
#[default]
Source,
Expansion(Arc<Expansion>),
}
impl Origin {
pub fn expansion(&self) -> Option<&Arc<Expansion>> {
match self {
Origin::Source => None,
Origin::Expansion(e) => Some(e),
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct Token {
pub kind: TokenKind,
pub range: SourceRange,
pub origin: Origin,
}
impl Token {
pub fn keyword(&self) -> Option<lex::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: lex::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, Default, Debug)]
pub struct Expansions {
entries: Vec<ExpansionSite>,
}
#[derive(Clone, Debug)]
struct ExpansionSite {
invocation: SourceRange,
name: String,
definition: SourceRange,
}
impl Expansions {
fn record(&mut self, range: SourceRange, name: &str, definition: SourceRange) {
self.entries.push(ExpansionSite {
invocation: range,
name: name.to_owned(),
definition,
});
}
fn enclosing(&self, pos: Pos) -> Option<&ExpansionSite> {
let mut best: Option<&ExpansionSite> = None;
for entry in &self.entries {
if entry.invocation.start > pos || entry.invocation.end < pos {
continue;
}
match best {
Some(b) if b.invocation.len() <= entry.invocation.len() => {}
_ => best = Some(entry),
}
}
best
}
pub fn annotate(&self, diags: &mut Diagnostics) {
if self.entries.is_empty() {
return;
}
for diag in diags.items_mut() {
if let Some(site) = self.enclosing(diag.range.start) {
diag.notes.push(crate::diag::Note {
message: format!("in expansion of macro '{}', defined", site.name),
range: Some(site.definition),
});
}
}
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[derive(Clone, Default, PartialEq, Eq, Debug)]
struct HideSet(Option<Arc<Vec<String>>>);
impl HideSet {
fn contains(&self, name: &str) -> bool {
match &self.0 {
None => false,
Some(names) => names.iter().any(|n| n == name),
}
}
fn add(&self, name: &str) -> HideSet {
if self.contains(name) {
return self.clone();
}
let mut names = match &self.0 {
None => Vec::with_capacity(1),
Some(names) => (**names).clone(),
};
names.push(name.to_owned());
HideSet(Some(Arc::new(names)))
}
fn intersect(&self, other: &HideSet) -> HideSet {
let (Some(a), Some(b)) = (&self.0, &other.0) else {
return HideSet::default();
};
let names: Vec<String> = a.iter().filter(|n| b.contains(n)).cloned().collect();
if names.is_empty() {
HideSet::default()
} else {
HideSet(Some(Arc::new(names)))
}
}
fn union(&self, other: &HideSet) -> HideSet {
let Some(names) = &other.0 else {
return self.clone();
};
let mut out = self.clone();
for name in names.iter() {
out = out.add(name);
}
out
}
}
#[derive(Clone, Debug)]
struct PTok {
kind: TokenKind,
range: SourceRange,
bol: bool,
space: bool,
origin: Origin,
hide: HideSet,
errors: Vec<Diagnostic>,
}
impl PTok {
fn from_lexed(tok: &lex::Token) -> Self {
Self {
kind: tok.kind.clone(),
range: tok.range,
bol: tok.bol,
space: tok.preceded_by_space,
origin: Origin::Source,
hide: HideSet::default(),
errors: tok.errors.clone(),
}
}
fn is_eof(&self) -> bool {
self.kind == TokenKind::Eof
}
fn is_punct(&self, p: Punct) -> bool {
self.kind == TokenKind::Punct(p)
}
fn name(&self) -> Option<&str> {
self.kind.macro_name()
}
fn spelling(&self) -> &str {
self.kind.spelling()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Builtin {
Line,
File,
FileName,
IncludeLevel,
Counter,
}
#[derive(Debug)]
struct MacroDef {
params: Option<Vec<String>>,
variadic: bool,
va_name: Option<String>,
body: Vec<PTok>,
name_range: SourceRange,
predefined: bool,
builtin: Option<Builtin>,
}
impl MacroDef {
fn same_as(&self, other: &MacroDef) -> bool {
if self.params != other.params
|| self.variadic != other.variadic
|| self.va_name != other.va_name
{
return false;
}
if self.body.len() != other.body.len() {
return false;
}
self.body
.iter()
.zip(&other.body)
.enumerate()
.all(|(i, (a, b))| a.spelling() == b.spelling() && (i == 0 || a.space == b.space))
}
fn param_index(&self, name: &str) -> Option<usize> {
let params = self.params.as_ref()?;
if let Some(i) = params.iter().position(|p| p == name) {
return Some(i);
}
let variable = name == VA_ARGS || self.va_name.as_deref() == Some(name);
(self.variadic && variable).then_some(params.len())
}
fn va_index(&self) -> Option<usize> {
self.variadic
.then(|| self.params.as_ref().map_or(0, Vec::len))
}
}
struct ParamList {
params: Vec<String>,
variadic: bool,
va_name: Option<String>,
used: usize,
}
const VA_ARGS: &str = "__VA_ARGS__";
const VA_OPT: &str = "__VA_OPT__";
const PRAGMA_OPERATOR: &str = "_Pragma";
fn destringize(text: &str) -> String {
let inner = text
.strip_prefix("L\"")
.or_else(|| text.strip_prefix('"'))
.and_then(|rest| rest.strip_suffix('"'))
.unwrap_or(text);
let mut out = String::with_capacity(inner.len());
let mut chars = inner.chars();
while let Some(c) = chars.next() {
if c != '\\' {
out.push(c);
continue;
}
match chars.next() {
Some(next @ ('"' | '\\')) => out.push(next),
Some(next) => {
out.push('\\');
out.push(next);
}
None => out.push('\\'),
}
}
out
}
const MAX_EXPANSION_DEPTH: u32 = 200;
const MAX_INCLUDE_DEPTH: usize = 200;
const EMBED_NOT_FOUND: u128 = 0;
const EMBED_FOUND: u128 = 1;
const EMBED_EMPTY: u128 = 2;
const MAX_EXPANDED_TOKENS: usize = 4_000_000;
#[derive(Clone, Debug)]
pub struct Context {
pub text: String,
pub base: Pos,
pub file_name: String,
pub first_line: usize,
pub dir: Option<std::path::PathBuf>,
pub next_base: Pos,
pub target_pragmas: TargetPragmas,
}
impl Context {
pub fn new(text: impl Into<String>, base: Pos) -> Self {
let text = text.into();
let next_base = base
.saturating_add(text.len() as Pos)
.saturating_add(FILE_GAP);
Self {
text,
base,
file_name: DEFAULT_FILE_NAME.to_owned(),
first_line: 1,
dir: None,
next_base,
target_pragmas: TargetPragmas::default(),
}
}
}
const FILE_GAP: Pos = 1;
pub const DEFAULT_FILE_NAME: &str = "<c99!>";
#[derive(Clone, Debug)]
pub struct IncludedFile {
pub name: String,
pub text: String,
pub base: Pos,
pub directive: SourceRange,
}
#[derive(Clone, Debug, Default)]
struct EmbedParams {
limit: Option<usize>,
prefix: Vec<PTok>,
suffix: Vec<PTok>,
if_empty: Vec<PTok>,
}
#[derive(Clone, Debug)]
pub struct SafeName {
pub name: String,
pub range: SourceRange,
}
#[derive(Debug)]
pub struct Preprocessed {
pub tokens: Vec<Token>,
pub expansions: Expansions,
pub included: Vec<IncludedFile>,
pub user_headers: Vec<std::path::PathBuf>,
pub embedded_files: Vec<std::path::PathBuf>,
pub link_libraries: Vec<String>,
pub safe_functions: Vec<SafeName>,
pub export: bool,
pub no_std: bool,
pub crate_path: Option<String>,
pub pack_events: Vec<(usize, Option<u32>)>,
}
#[derive(Clone, Debug, Default)]
pub struct PackMap {
events: Vec<(usize, Option<u32>)>,
}
impl PackMap {
pub fn new(events: Vec<(usize, Option<u32>)>) -> Self {
Self { events }
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
pub fn at(&self, index: usize) -> Option<u32> {
let at = self.events.partition_point(|(pos, _)| *pos <= index);
self.events[..at].last().and_then(|(_, value)| *value)
}
}
pub fn preprocess(
tokens: &[lex::Token],
ctx: &Context,
options: &Options,
diags: &mut Diagnostics,
) -> Preprocessed {
let mut pp = Pp::new(tokens, ctx, options, diags);
pp.run();
Preprocessed {
tokens: pp.out,
expansions: pp.expansions,
included: pp.included,
user_headers: pp.user_headers,
embedded_files: pp.embedded_files,
link_libraries: pp.link_libraries,
safe_functions: pp.safe_functions,
export: pp.export,
no_std: pp.no_std,
crate_path: pp.crate_path,
pack_events: pp.pack_events,
}
}
#[derive(Clone, Debug, Default)]
pub struct TargetPragmas {
pub at: Vec<Pos>,
pub applied: bool,
}
impl TargetPragmas {
fn scanned(&self, range: SourceRange) -> bool {
self.at.contains(&range.start)
}
}
pub fn scan_target_pragma(
tokens: &[lex::Token],
options: &mut Options,
diags: &mut Diagnostics,
) -> (TargetPragmas, bool) {
let mut found = TargetPragmas::default();
let mut chosen: Option<(String, SourceRange)> = None;
let mut failed = false;
for (i, hash) in tokens.iter().enumerate() {
if !hash.bol || !hash.is_punct(Punct::Hash) {
continue;
}
let words: Vec<&lex::Token> = tokens[i + 1..]
.iter()
.take_while(|t| !t.bol && !matches!(t.kind, TokenKind::Eof))
.take(5)
.collect();
let [pragma, cinrs, option, rest @ ..] = words.as_slice() else {
continue;
};
if pragma.ident() != Some("pragma")
|| cinrs.ident() != Some("cinrs")
|| option.ident() != Some("target")
{
continue;
}
found.at.push(hash.range.start);
let range = SourceRange::new(hash.range.start, option.range.end);
let Some(triple) = target_pragma_triple(rest, range, diags) else {
failed = true;
continue;
};
match &chosen {
Some((first, _)) if *first == triple => {}
Some((first, _)) => {
diags.error(
range,
format!(
"this unit is already translated for '{first}' by an earlier \
#pragma cinrs target"
),
);
failed = true;
}
None => chosen = Some((triple, range)),
}
}
let Some((triple, range)) = chosen.filter(|_| !failed) else {
return (found, false);
};
let source = TargetSource::Pragma(triple);
match TargetModel::from_triple(source.triple().expect("Pragma carries its triple")) {
Ok(model) => {
let relex = options.target != model;
options.target = model;
options.target_source = source;
found.applied = true;
(found, relex)
}
Err(unknown) => {
diags.error(range, unknown.message(&source));
(found, false)
}
}
}
fn target_pragma_triple(
rest: &[&lex::Token],
range: SourceRange,
diags: &mut Diagnostics,
) -> Option<String> {
let Some(tok) = rest.first() else {
diags.error(range, "#pragma cinrs target needs a string literal");
return None;
};
let TokenKind::Str(lit) = &tok.kind else {
diags.error(
tok.range,
format!(
"#pragma cinrs target needs a string literal, found {}",
tok.kind.describe()
),
);
return None;
};
let Some(bytes) = lit.as_bytes() else {
diags.error(
tok.range,
"#pragma cinrs target does not take a wide string literal",
);
return None;
};
let value = String::from_utf8_lossy(&bytes).into_owned();
if value.is_empty() {
diags.error(tok.range, "#pragma cinrs target was given an empty string");
return None;
}
if let Some(extra) = rest.get(1) {
diags.error(
extra.range,
format!(
"unexpected {} after #pragma cinrs target",
extra.kind.describe()
),
);
}
Some(value)
}
struct Cond {
range: SourceRange,
outer_active: bool,
taken: bool,
active: bool,
seen_else: bool,
}
struct LineDirective {
at: usize,
line: usize,
name: String,
}
struct FileEntry {
text: String,
base: Pos,
line_starts: Vec<u32>,
first_line: usize,
name: String,
lines: Vec<LineDirective>,
}
impl FileEntry {
fn new(text: String, base: Pos, first_line: usize, name: String) -> Self {
let mut line_starts = vec![0u32];
for (i, b) in text.bytes().enumerate() {
if b == b'\n' {
line_starts.push(i as u32 + 1);
}
}
Self {
text,
base,
line_starts,
first_line: first_line.max(1),
name,
lines: Vec::new(),
}
}
fn physical_line(&self, local: Pos) -> usize {
self.line_starts
.partition_point(|start| *start <= local)
.saturating_sub(1)
}
fn directive_for(&self, index: usize) -> Option<&LineDirective> {
let after = self.lines.partition_point(|d| d.at < index);
self.lines[..after].last()
}
fn line_of(&self, local: Pos) -> usize {
let index = self.physical_line(local);
match self.directive_for(index) {
Some(d) => d.line + (index - d.at - 1),
None => self.first_line + index,
}
}
fn name_of(&self, local: Pos) -> &str {
match self.directive_for(self.physical_line(local)) {
Some(d) => &d.name,
None => &self.name,
}
}
}
struct OpenFile {
input: Vec<PTok>,
pos: usize,
origin: include::Origin,
found_in: Option<include::Entry>,
key: String,
cond_base: usize,
}
struct Pp<'a> {
files: Vec<FileEntry>,
open: Vec<OpenFile>,
pending: Vec<PTok>,
out: Vec<Token>,
macros: HashMap<String, Arc<MacroDef>>,
conds: Vec<Cond>,
diags: &'a mut Diagnostics,
expansions: Expansions,
reported: HashSet<(Pos, Pos, String)>,
base: Pos,
lex_options: LexOptions,
gating: Gating,
depth: u32,
budget: usize,
aborted: bool,
next_base: Pos,
included: Vec<IncludedFile>,
search: include::SearchPaths,
once: HashSet<String>,
macro_stacks: HashMap<String, Vec<Option<Arc<MacroDef>>>>,
poisoned: HashSet<String>,
counter: u64,
pack: Option<u32>,
pack_stack: Vec<Option<u32>>,
pack_events: Vec<(usize, Option<u32>)>,
base_file: String,
guards: HashMap<String, String>,
user_headers: Vec<std::path::PathBuf>,
embedded_files: Vec<std::path::PathBuf>,
link_libraries: Vec<String>,
safe_functions: Vec<SafeName>,
export: bool,
no_std: bool,
crate_path: Option<String>,
target_source: TargetSource,
target: crate::target::TargetModel,
target_pragmas: TargetPragmas,
model_observed: bool,
}
impl<'a> Pp<'a> {
fn new(
tokens: &[lex::Token],
ctx: &'a Context,
options: &Options,
diags: &'a mut Diagnostics,
) -> Self {
let root = FileEntry::new(
ctx.text.clone(),
ctx.base,
ctx.first_line,
ctx.file_name.clone(),
);
let mut input: Vec<PTok> = tokens.iter().map(PTok::from_lexed).collect();
if input.is_empty() {
input.push(eof_token(ctx.base));
}
let mut pp = Pp {
files: vec![root],
open: vec![OpenFile {
input,
pos: 0,
origin: match &ctx.dir {
Some(dir) => include::Origin::Dir(dir.clone()),
None => include::Origin::Unknown,
},
found_in: None,
key: ctx.file_name.clone(),
cond_base: 0,
}],
pending: Vec::new(),
out: Vec::new(),
macros: HashMap::new(),
conds: Vec::new(),
diags,
expansions: Expansions::default(),
reported: HashSet::new(),
base: ctx.base,
lex_options: options.into(),
gating: options.gating(),
depth: 0,
budget: MAX_EXPANDED_TOKENS,
aborted: false,
next_base: ctx.next_base,
included: Vec::new(),
search: include::SearchPaths::new(&options.include_paths),
once: HashSet::new(),
macro_stacks: HashMap::new(),
poisoned: HashSet::new(),
counter: 0,
pack: None,
pack_stack: Vec::new(),
pack_events: Vec::new(),
base_file: ctx.file_name.clone(),
guards: HashMap::new(),
user_headers: Vec::new(),
embedded_files: Vec::new(),
link_libraries: Vec::new(),
safe_functions: Vec::new(),
export: false,
no_std: false,
crate_path: None,
target_source: options.target_source.clone(),
target: options.target,
target_pragmas: ctx.target_pragmas.clone(),
model_observed: false,
};
pp.define_predefined(options);
if options.system_include.is_on() {
let range = SourceRange::new(ctx.base, ctx.base + ctx.text.len() as Pos);
pp.enable_system_include(options.system_include, range);
}
pp
}
fn cur(&self) -> &OpenFile {
self.open
.last()
.expect("the root file is only closed when the run ends")
}
fn cur_mut(&mut self) -> &mut OpenFile {
self.open
.last_mut()
.expect("the root file is only closed when the run ends")
}
fn ahead(&self) -> &PTok {
let file = self.cur();
&file.input[file.pos]
}
fn peek(&self, allow_input: bool) -> Option<&PTok> {
if let Some(t) = self.pending.last() {
return Some(t);
}
allow_input.then(|| self.ahead())
}
fn bump(&mut self, allow_input: bool) -> Option<PTok> {
if let Some(t) = self.pending.pop() {
return Some(t);
}
if !allow_input {
return None;
}
let tok = self.ahead().clone();
if !tok.is_eof() {
self.cur_mut().pos += 1;
}
self.report_lexical_errors(&tok);
Some(tok)
}
fn at_directive(&self) -> bool {
let tok = self.ahead();
tok.bol && tok.is_punct(Punct::Hash)
}
fn skipping(&self) -> bool {
self.conds.last().is_some_and(|c| !c.active)
}
fn run(&mut self) {
loop {
if self.pending.is_empty() {
if self.ahead().is_eof() {
if self.open.len() > 1 {
self.close_file();
continue;
}
self.finish();
return;
}
if self.at_directive() {
self.directive();
continue;
}
if self.skipping() {
self.cur_mut().pos += 1;
continue;
}
}
let Some(tok) = self.bump(true) else {
unreachable!("reading the file is always allowed here");
};
if tok.is_eof() {
continue;
}
if tok.name().is_some() && self.try_expand(&tok, true) {
continue;
}
if tok.name() == Some(PRAGMA_OPERATOR) && self.pragma_operator(&tok) {
continue;
}
self.emit(tok);
}
}
fn pragma_operator(&mut self, tok: &PTok) -> bool {
if !self.peek(true).is_some_and(|t| t.is_punct(Punct::LParen)) {
return false;
}
self.require_standard(Standard::C99, "'_Pragma'", tok.range);
self.bump(true);
let literal = self.bump(true);
let Some(text) = literal.as_ref().and_then(|t| match &t.kind {
TokenKind::Str(lit) => Some(destringize(&lit.text)),
_ => None,
}) else {
self.diags
.error(tok.range, "'_Pragma' takes one string literal");
return true;
};
if !self.bump(true).is_some_and(|t| t.is_punct(Punct::RParen)) {
self.diags.error(tok.range, "missing ')' after '_Pragma'");
return true;
}
let tokens: Vec<PTok> = lex::lex_text(&text, tok.range.start, &self.lex_options)
.iter()
.filter(|t| !matches!(t.kind, TokenKind::Eof))
.map(PTok::from_lexed)
.collect();
self.pragma(&tokens, tok.range);
true
}
fn close_file(&mut self) {
if !self.skipping() {
let eof = self.ahead().clone();
self.report_lexical_errors(&eof);
}
let base = self.cur().cond_base;
for cond in self.conds.drain(base..).collect::<Vec<_>>() {
self.diags
.error(cond.range, "unterminated conditional directive");
}
self.open.pop();
}
fn finish(&mut self) {
let skipped = self.skipping();
for cond in std::mem::take(&mut self.conds) {
self.diags
.error(cond.range, "unterminated conditional directive");
}
let file = self.cur();
let mut eof = file.input[file.input.len() - 1].clone();
if skipped {
eof.errors.clear();
}
self.emit(eof);
}
fn emit(&mut self, mut tok: PTok) {
self.report_errors(&tok);
if matches!(tok.kind, TokenKind::Error(_)) {
return;
}
if let TokenKind::Ident(name) = &tok.kind {
if self.poisoned.contains(name) {
let range = tok.range;
let name = name.clone();
self.diags.error(
range,
format!("attempt to use the poisoned identifier '{name}'"),
);
}
if let Some(keyword) = gnu_keyword(name, self.gating.dialect) {
tok.kind = TokenKind::Keyword(keyword);
}
}
self.out.push(Token {
kind: tok.kind,
range: tok.range,
origin: tok.origin,
});
}
fn report_errors(&mut self, tok: &PTok) {
self.report_token_diags(tok, false);
}
fn report_lexical_errors(&mut self, tok: &PTok) {
self.report_token_diags(tok, true);
}
fn report_token_diags(&mut self, tok: &PTok, lexical_only: bool) {
for diag in &tok.errors {
if lexical_only && !diag.lexical {
continue;
}
let key = (diag.range.start, diag.range.end, diag.message.clone());
if self.reported.insert(key) {
self.diags.push(diag.clone());
}
}
}
fn file_at(&self, pos: Pos) -> &FileEntry {
&self.files[self.file_index(pos)]
}
fn file_index(&self, pos: Pos) -> usize {
self.files
.partition_point(|f| f.base <= pos)
.saturating_sub(1)
}
fn local_pos(file: &FileEntry, pos: Pos) -> Pos {
pos.saturating_sub(file.base).min(file.text.len() as Pos)
}
fn line_of(&self, pos: Pos) -> usize {
let file = self.file_at(pos);
file.line_of(Self::local_pos(file, pos))
}
fn file_name_of(&self, pos: Pos) -> &str {
let file = self.file_at(pos);
file.name_of(Self::local_pos(file, pos))
}
fn raw_text(&self, from: Pos, to: Pos) -> &str {
let file = self.file_at(from);
let start = from.saturating_sub(file.base) as usize;
let end = to.saturating_sub(file.base) as usize;
file.text.get(start..end).unwrap_or("")
}
}
fn gnu_keyword(name: &str, dialect: Dialect) -> Option<Keyword> {
let keyword = match name {
"__inline" | "__inline__" => Keyword::InlineGnu,
"__const" | "__const__" => Keyword::Const,
"__signed" | "__signed__" => Keyword::Signed,
"__volatile" | "__volatile__" => Keyword::Volatile,
"__restrict" | "__restrict__" => Keyword::RestrictGnu,
"__complex__" | "__complex" => Keyword::Complex,
"__attribute" | "__attribute__" => Keyword::Attribute,
"__extension__" => Keyword::Extension,
"__alignof" | "__alignof__" => Keyword::AlignofGnu,
"__typeof" | "__typeof__" => Keyword::TypeofGnu,
"__typeof_unqual__" | "__typeof_unqual" => Keyword::TypeofUnqualGnu,
"__asm" | "__asm__" => Keyword::Asm,
"__label__" => Keyword::Label,
"__auto_type" => Keyword::AutoType,
"__thread" => Keyword::ThreadGnu,
"__int128" => Keyword::Int128,
"__real" | "__real__" => Keyword::RealGnu,
"__imag" | "__imag__" => Keyword::ImagGnu,
"asm" if dialect.is_gnu() => Keyword::Asm,
"typeof" if dialect.is_gnu() => Keyword::TypeofGnu,
_ => return None,
};
Some(keyword)
}
fn eof_token(base: Pos) -> PTok {
PTok {
kind: TokenKind::Eof,
range: SourceRange::at(base),
bol: true,
space: true,
origin: Origin::Source,
hide: HideSet::default(),
errors: Vec::new(),
}
}
struct Args {
raw: Vec<Vec<PTok>>,
expanded: Vec<Option<Vec<PTok>>>,
}
impl Args {
fn new(raw: Vec<Vec<PTok>>) -> Self {
Self {
expanded: vec![None; raw.len()],
raw,
}
}
fn get(&self, index: usize) -> &[PTok] {
self.raw.get(index).map_or(&[], Vec::as_slice)
}
}
enum Piece {
Tok(PTok),
Placemarker,
}
impl Pp<'_> {
fn try_expand(&mut self, tok: &PTok, allow_input: bool) -> bool {
if self.aborted {
return false;
}
let Some(name) = tok.name() else {
return false;
};
if tok.hide.contains(name) {
return false;
}
let Some(def) = self.macros.get(name).cloned() else {
return false;
};
let name = name.to_owned();
if let Some(builtin) = def.builtin {
let value = self.builtin_token(builtin, tok, &def, &name);
self.push_pending(vec![value], tok.space);
return true;
}
let Some(params) = &def.params else {
let hide = tok.hide.add(&name);
let exp = self.expansion_of(&name, tok.range, &def, tok);
let mut args = Args::new(Vec::new());
let body = self.subst(&def, &mut args, &hide, tok.range, &exp);
self.push_pending(body, tok.space);
if !def.predefined {
self.expansions.record(tok.range, &name, def.name_range);
}
return true;
};
if !self
.peek(allow_input)
.is_some_and(|t| t.is_punct(Punct::LParen))
{
return false;
}
let params = params.clone();
self.bump(allow_input);
let Some((mut raw, rparen)) = self.collect_args(&def, ¶ms, tok.range, allow_input)
else {
return true;
};
let invocation = tok.range.join(rparen.range);
if !self.check_arity(&def, ¶ms, raw.len(), &name, invocation) {
return true;
}
if def.variadic {
while raw.len() <= params.len() {
raw.push(Vec::new());
}
}
let hide = tok.hide.intersect(&rparen.hide).add(&name);
let exp = self.expansion_of(&name, invocation, &def, tok);
let mut args = Args::new(raw);
let body = self.subst(&def, &mut args, &hide, invocation, &exp);
self.push_pending(body, tok.space);
if !def.predefined {
self.expansions.record(invocation, &name, def.name_range);
}
true
}
fn expansion_of(
&self,
name: &str,
invocation: SourceRange,
def: &MacroDef,
tok: &PTok,
) -> Arc<Expansion> {
Arc::new(Expansion {
name: name.to_owned(),
invocation,
definition: def.name_range,
parent: tok.origin.expansion().cloned(),
})
}
fn push_pending(&mut self, mut toks: Vec<PTok>, space: bool) {
if self.budget < toks.len() {
if !self.aborted {
let range = toks.first().map_or(SourceRange::at(self.base), |t| t.range);
self.diags
.error(range, "macro expansion produced too many tokens");
self.aborted = true;
}
return;
}
self.budget -= toks.len();
if let Some(first) = toks.first_mut() {
first.space = space;
first.bol = false;
}
self.pending.extend(toks.into_iter().rev());
}
fn collect_args(
&mut self,
def: &MacroDef,
params: &[String],
name_range: SourceRange,
allow_input: bool,
) -> Option<(Vec<Vec<PTok>>, PTok)> {
let mut args: Vec<Vec<PTok>> = vec![Vec::new()];
let mut depth = 0u32;
loop {
let Some(tok) = self.bump(allow_input).filter(|t| !t.is_eof()) else {
self.diags.error(
name_range,
"unterminated argument list of a function-like macro",
);
return None;
};
if tok.is_punct(Punct::LParen) {
depth += 1;
} else if tok.is_punct(Punct::RParen) {
if depth == 0 {
if !def.variadic && params.is_empty() && args.len() == 1 && args[0].is_empty() {
args.clear();
}
return Some((args, tok));
}
depth -= 1;
} else if tok.is_punct(Punct::Comma)
&& depth == 0
&& (!def.variadic || args.len() <= params.len())
{
args.push(Vec::new());
continue;
}
args.last_mut()
.expect("the argument list is never empty")
.push(tok);
}
}
fn check_arity(
&mut self,
def: &MacroDef,
params: &[String],
given: usize,
name: &str,
invocation: SourceRange,
) -> bool {
let wanted = params.len();
let ok = if def.variadic {
given >= wanted
} else {
given == wanted
};
if ok {
return true;
}
let message = if given < wanted {
let least = if def.variadic { "at least " } else { "" };
format!("macro '{name}' requires {least}{wanted} arguments, but only {given} given")
} else {
format!("macro '{name}' passed {given} arguments, but takes just {wanted}")
};
self.diags.push(
Diagnostic::error(invocation, message)
.with_note_at(def.name_range, format!("macro '{name}' defined")),
);
false
}
fn subst(
&mut self,
def: &MacroDef,
args: &mut Args,
hide: &HideSet,
invocation: SourceRange,
exp: &Arc<Expansion>,
) -> Vec<PTok> {
let expanded;
let body: &[PTok] = match expand_va_opt(def, args) {
Some(tokens) => {
expanded = tokens;
&expanded
}
None => &def.body,
};
let mut pieces: Vec<Piece> = Vec::with_capacity(body.len());
let mut i = 0;
while i < body.len() {
let tok = &body[i];
if def.params.is_some()
&& tok.is_punct(Punct::Hash)
&& let Some(next) = body.get(i + 1)
&& let Some(index) = next.name().and_then(|n| def.param_index(n))
{
let kind = self.stringify(args.get(index));
pieces.push(Piece::Tok(self.synthetic(kind, tok, invocation, exp)));
i += 2;
continue;
}
if tok.is_punct(Punct::Comma)
&& body.get(i + 1).is_some_and(|t| t.is_punct(Punct::HashHash))
&& let Some(index) = body
.get(i + 2)
.and_then(PTok::name)
.and_then(|n| def.param_index(n))
&& Some(index) == def.va_index()
{
if !args.get(index).is_empty() {
let mut comma = tok.clone();
comma.range = invocation;
comma.origin = Origin::Expansion(exp.clone());
pieces.push(Piece::Tok(comma));
let arg = self.expanded_arg(args, index);
pieces.extend(arg.into_iter().map(Piece::Tok));
}
i += 3;
continue;
}
if tok.is_punct(Punct::HashHash)
&& let Some(next) = body.get(i + 1)
{
let rhs = paste_operand(def, args, next);
self.paste_pieces(&mut pieces, rhs, invocation, exp);
i += 2;
continue;
}
if let Some(index) = tok.name().and_then(|n| def.param_index(n)) {
let raw = body.get(i + 1).is_some_and(|t| t.is_punct(Punct::HashHash));
if raw {
let arg = args.get(index).to_vec();
if arg.is_empty() {
pieces.push(Piece::Placemarker);
} else {
pieces.extend(arg.into_iter().map(Piece::Tok));
}
} else {
let arg = self.expanded_arg(args, index);
pieces.extend(arg.into_iter().map(Piece::Tok));
}
i += 1;
continue;
}
let mut copy = tok.clone();
copy.range = invocation;
copy.origin = Origin::Expansion(exp.clone());
pieces.push(Piece::Tok(copy));
i += 1;
}
pieces
.into_iter()
.filter_map(|p| match p {
Piece::Tok(mut t) => {
t.hide = t.hide.union(hide);
Some(t)
}
Piece::Placemarker => None,
})
.collect()
}
fn paste_pieces(
&mut self,
pieces: &mut Vec<Piece>,
mut rhs: Vec<Piece>,
invocation: SourceRange,
exp: &Arc<Expansion>,
) {
if rhs.is_empty() {
return;
}
let head = rhs.remove(0);
let left = pieces.pop();
let joined = match (left, head) {
(None, head) => head,
(Some(Piece::Placemarker), head) => head,
(Some(left), Piece::Placemarker) => left,
(Some(Piece::Tok(l)), Piece::Tok(r)) => match self.paste(&l, &r, invocation) {
Some(kind) => Piece::Tok(self.synthetic(kind, &l, invocation, exp)),
None => {
pieces.push(Piece::Tok(l));
Piece::Tok(r)
}
},
};
pieces.push(joined);
pieces.extend(rhs);
}
fn paste(&mut self, lhs: &PTok, rhs: &PTok, invocation: SourceRange) -> Option<TokenKind> {
let text = format!("{}{}", lhs.spelling(), rhs.spelling());
if text.is_empty() {
return None;
}
let tokens = lex::lex_text(&text, 0, &self.lex_options);
let valid = tokens.len() == 2
&& tokens[0].errors.is_empty()
&& !matches!(tokens[0].kind, TokenKind::Error(_))
&& tokens[0].range.end as usize == text.len();
if !valid {
self.diags.error(
invocation,
format!(
"pasting '{}' and '{}' does not give a valid token",
lhs.spelling(),
rhs.spelling()
),
);
return None;
}
Some(tokens[0].kind.clone())
}
fn synthetic(
&self,
kind: TokenKind,
like: &PTok,
invocation: SourceRange,
exp: &Arc<Expansion>,
) -> PTok {
PTok {
kind,
range: invocation,
bol: false,
space: like.space,
origin: Origin::Expansion(exp.clone()),
hide: like.hide.clone(),
errors: Vec::new(),
}
}
fn expanded_arg(&mut self, args: &mut Args, index: usize) -> Vec<PTok> {
if let Some(Some(done)) = args.expanded.get(index) {
return done.clone();
}
let raw = args.get(index).to_vec();
let done = self.expand_sequence(raw);
if let Some(slot) = args.expanded.get_mut(index) {
*slot = Some(done.clone());
}
done
}
fn expand_sequence(&mut self, toks: Vec<PTok>) -> Vec<PTok> {
if toks.is_empty() {
return toks;
}
self.depth += 1;
if self.depth > MAX_EXPANSION_DEPTH {
self.depth -= 1;
if !self.aborted {
let range = toks[0].range;
self.diags.error(range, "macro arguments nest too deeply");
self.aborted = true;
}
return toks;
}
let saved = std::mem::replace(&mut self.pending, toks.into_iter().rev().collect());
let mut out = Vec::new();
while let Some(tok) = self.pending.pop() {
if tok.name().is_some() && self.try_expand(&tok, false) {
continue;
}
out.push(tok);
}
self.pending = saved;
self.depth -= 1;
out
}
fn builtin_token(&mut self, builtin: Builtin, tok: &PTok, def: &MacroDef, name: &str) -> PTok {
let kind = match builtin {
Builtin::Line => {
let line = self.line_of(tok.range.start) as u128;
TokenKind::Int(IntLit {
value: line,
base: NumBase::Decimal,
unsigned: false,
long: LongKind::None,
text: line.to_string(),
})
}
Builtin::File => {
let file = self.file_name_of(tok.range.start).to_owned();
string_token_kind(&file)
}
Builtin::FileName => {
let file = self.file_name_of(tok.range.start);
let base = file
.rsplit_once(['/', '\\'])
.map_or(file, |(_, base)| base)
.to_owned();
string_token_kind(&base)
}
Builtin::IncludeLevel => int_token_kind((self.open.len() - 1) as u128),
Builtin::Counter => {
let value = self.counter;
self.counter += 1;
int_token_kind(u128::from(value))
}
};
let exp = self.expansion_of(name, tok.range, def, tok);
PTok {
kind,
range: tok.range,
bol: false,
space: tok.space,
origin: Origin::Expansion(exp),
hide: tok.hide.add(name),
errors: Vec::new(),
}
}
}
fn relex_string(text: String, options: &LexOptions) -> TokenKind {
let tokens = lex::lex_text(&text, 0, options);
if tokens.len() == 2
&& tokens[0].errors.is_empty()
&& matches!(tokens[0].kind, TokenKind::Str(_))
&& tokens[0].range.end as usize == text.len()
{
return tokens[0].kind.clone();
}
let inner = text.trim_matches('"');
TokenKind::Str(StrLit {
kind: StrKind::Narrow,
values: inner.bytes().map(u32::from).collect(),
text,
})
}
impl Pp<'_> {
fn stringify(&self, arg: &[PTok]) -> TokenKind {
let mut text = String::from('"');
for (i, tok) in arg.iter().enumerate() {
if i > 0 && tok.space {
text.push(' ');
}
let quoted = matches!(tok.kind, TokenKind::Char(_) | TokenKind::Str(_));
for c in tok.spelling().chars() {
if quoted && (c == '"' || c == '\\') {
text.push('\\');
}
text.push(c);
}
}
text.push('"');
relex_string(text, &self.lex_options)
}
}
fn paste_operand(def: &MacroDef, args: &Args, tok: &PTok) -> Vec<Piece> {
let Some(index) = tok.name().and_then(|n| def.param_index(n)) else {
return vec![Piece::Tok(tok.clone())];
};
let arg = args.get(index);
if arg.is_empty() {
return vec![Piece::Placemarker];
}
arg.iter().cloned().map(Piece::Tok).collect()
}
const MAX_LINE_NUMBER: u64 = 2_147_483_647;
fn digit_sequence(kind: &TokenKind) -> Option<u64> {
let TokenKind::Int(lit) = kind else {
return None;
};
if lit.text.is_empty() || !lit.text.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
Some(lit.text.parse::<u64>().unwrap_or(u64::MAX))
}
fn is_line_form(rest: &[PTok]) -> bool {
let Some(first) = rest.first() else {
return false;
};
if digit_sequence(&first.kind).is_none() {
return false;
}
match rest.len() {
1 => true,
2 => matches!(&rest[1].kind, TokenKind::Str(lit) if lit.kind == StrKind::Narrow),
_ => false,
}
}
fn int_token_kind(value: u128) -> TokenKind {
TokenKind::Int(IntLit {
value,
base: NumBase::Decimal,
unsigned: false,
long: LongKind::None,
text: value.to_string(),
})
}
fn string_token_kind(value: &str) -> TokenKind {
TokenKind::Str(StrLit {
kind: StrKind::Narrow,
values: value.bytes().map(u32::from).collect(),
text: quote_c_string(value),
})
}
fn quote_c_string(text: &str) -> String {
let mut out = String::with_capacity(text.len() + 2);
out.push('"');
for c in text.chars() {
if c == '"' || c == '\\' {
out.push('\\');
}
out.push(c);
}
out.push('"');
out
}
impl Pp<'_> {
fn directive(&mut self) {
let hash = self.ahead().clone();
self.cur_mut().pos += 1;
let start = self.cur().pos;
while !self.ahead().is_eof() && !self.ahead().bol {
self.cur_mut().pos += 1;
}
let file = self.cur();
let line: Vec<PTok> = file.input[start..file.pos].to_vec();
if !self.skipping() {
self.report_lexical_errors(&hash);
for tok in &line {
self.report_lexical_errors(tok);
}
}
let Some(first) = line.first() else {
return;
};
let range = hash.range.join(first.range);
let Some(name) = first.name() else {
if matches!(first.kind, TokenKind::Int(_)) {
if !self.skipping() {
self.line_directive(&line, range, true);
}
return;
}
if !self.skipping() {
self.diags.error(
range,
format!(
"invalid preprocessing directive after '#': {}",
first.kind.describe()
),
);
}
return;
};
let rest = &line[1..];
match name {
"if" => self.open_cond(range, |pp| pp.eval_condition(rest, range)),
"ifdef" | "ifndef" => {
let want = name == "ifdef";
self.open_cond(range, |pp| {
pp.macro_name_operand(rest, range, name)
.is_some_and(|n| pp.macros.contains_key(&n) == want)
});
}
"elif" => self.elif(range, "elif", |pp| pp.eval_condition(rest, range)),
"elifdef" | "elifndef" => {
self.require_standard(Standard::C23, &format!("'#{name}'"), range);
let want = name == "elifdef";
self.elif(range, name, |pp| {
pp.macro_name_operand(rest, range, name)
.is_some_and(|n| pp.macros.contains_key(&n) == want)
});
}
"else" => self.else_(rest, range),
"endif" => self.endif(range),
_ if self.skipping() => {
}
"define" => self.define(rest, range),
"undef" => self.undef(rest, range),
"include" => self.include(&line, range, false),
"include_next" => self.include(&line, range, true),
"error" => {
let text = self.directive_text(&line, 1);
let message = if text.is_empty() {
"#error".to_owned()
} else {
format!("#error {text}")
};
self.diags.error(range, message);
}
"warning" => {
let text = self.directive_text(&line, 1);
self.diags.warning(range, format!("#warning {text}"));
}
"pragma" => self.pragma(rest, range),
"embed" => {
self.require_standard(Standard::C23, "'#embed'", range);
self.embed(rest, range);
}
"line" => self.line_directive(rest, range, false),
"ident" | "sccs" => {}
other => {
self.diags
.error(range, format!("invalid preprocessing directive #{other}"));
}
}
}
fn line_directive(&mut self, rest: &[PTok], range: SourceRange, marker: bool) {
let what = if marker { "line marker" } else { "#line" };
let expanded: Vec<PTok>;
let toks: &[PTok] = if marker || is_line_form(rest) {
rest
} else {
expanded = self.expand_sequence(rest.to_vec());
&expanded
};
let Some(first) = toks.first() else {
self.diags
.error(range, format!("'{what}' requires a line number"));
return;
};
let Some(digits) = digit_sequence(&first.kind) else {
self.diags.error(
first.range,
format!(
"'{what}' requires a decimal line number, found {}",
first.kind.describe()
),
);
return;
};
if digits == 0 || digits > MAX_LINE_NUMBER {
self.diags.error(
first.range,
format!(
"the line number of '{what}' must be between 1 and {MAX_LINE_NUMBER}, \
not {digits}"
),
);
return;
}
let mut used = 1;
let mut name = None;
if let Some(tok) = toks.get(1) {
match &tok.kind {
TokenKind::Str(lit) if lit.kind == StrKind::Narrow => {
name = Some(
lit.values
.iter()
.map(|v| char::from_u32(*v).unwrap_or('\u{fffd}'))
.collect::<String>(),
);
used = 2;
}
_ if marker => {}
_ => {
self.diags.error(
tok.range,
format!(
"the file name of '{what}' must be an ordinary string literal, \
found {}",
tok.kind.describe()
),
);
return;
}
}
}
if !marker && toks.len() > used {
self.diags.warning(
toks[used].range,
format!("extra tokens at the end of '{what}'"),
);
}
self.set_line(range.start, digits as usize, name);
}
fn set_line(&mut self, pos: Pos, line: usize, name: Option<String>) {
let index = self.file_index(pos);
let local = Self::local_pos(&self.files[index], pos);
let file = &mut self.files[index];
let at = file.physical_line(local);
let name = match name {
Some(name) => name,
None => file.name_of(local).to_owned(),
};
while file.lines.last().is_some_and(|d| d.at >= at) {
file.lines.pop();
}
file.lines.push(LineDirective { at, line, name });
}
fn directive_text(&self, line: &[PTok], skip: usize) -> String {
let Some(first) = line.get(skip) else {
return String::new();
};
let last = line.last().unwrap_or(first);
self.raw_text(first.range.start, last.range.end)
.trim()
.to_owned()
}
fn pragma(&mut self, rest: &[PTok], range: SourceRange) {
match rest.first().and_then(PTok::name) {
Some("once") => {
let key = self.cur_key();
self.once.insert(key);
}
Some("cinrs") => self.cinrs_pragma(&rest[1..], range),
Some("pack") => self.pack_pragma(&rest[1..], range),
Some("push_macro") => self.push_macro_pragma(&rest[1..], range, true),
Some("pop_macro") => self.push_macro_pragma(&rest[1..], range, false),
Some("GCC") => self.gcc_pragma(&rest[1..], range),
_ => {}
}
}
fn gcc_pragma(&mut self, rest: &[PTok], range: SourceRange) {
match rest.first().and_then(PTok::name) {
Some("poison") => {
for tok in &rest[1..] {
match tok.name() {
Some(name) => {
self.poisoned.insert(name.to_owned());
}
None => self.diags.error(
tok.range,
format!(
"'#pragma GCC poison' takes identifiers, found {}",
tok.kind.describe()
),
),
}
}
}
Some("error") => {
let text = self.pragma_message(&rest[1..]);
self.diags.error(range, format!("#pragma GCC error {text}"));
}
Some("warning") => {
let text = self.pragma_message(&rest[1..]);
self.diags
.warning(range, format!("#pragma GCC warning {text}"));
}
_ => {}
}
}
fn pragma_message(&self, rest: &[PTok]) -> String {
match rest.first() {
Some(tok) => tok.spelling().to_owned(),
None => String::new(),
}
}
fn push_macro_pragma(&mut self, rest: &[PTok], range: SourceRange, push: bool) {
let what = if push { "push_macro" } else { "pop_macro" };
let inner = match rest {
[tok] if tok.is_punct(Punct::LParen) => None,
_ => rest
.iter()
.find_map(|tok| match &tok.kind {
TokenKind::Str(lit) => lit.as_bytes(),
_ => None,
})
.map(|bytes| String::from_utf8_lossy(&bytes).into_owned()),
};
let Some(name) = inner.filter(|name| !name.is_empty()) else {
self.diags.error(
range,
format!("#pragma {what} needs a string literal naming a macro"),
);
return;
};
if push {
let saved = self.macros.get(&name).cloned();
self.macro_stacks.entry(name).or_default().push(saved);
return;
}
match self.macro_stacks.get_mut(&name).and_then(Vec::pop) {
Some(Some(def)) => {
self.macros.insert(name, def);
}
Some(None) => {
self.macros.remove(&name);
}
None => {}
}
}
fn pack_pragma(&mut self, rest: &[PTok], range: SourceRange) {
let bad = |pp: &mut Self, at: SourceRange| {
pp.diags.error(
at,
"#pragma pack expects '(N)', '(push, N)', '(push)', '(pop)' or '()', \
where N is a power of two up to 16",
);
};
if !rest.first().is_some_and(|t| t.is_punct(Punct::LParen))
|| !rest.last().is_some_and(|t| t.is_punct(Punct::RParen))
|| rest.len() < 2
{
bad(self, range);
return;
}
let inner = &rest[1..rest.len() - 1];
let value = |pp: &mut Self, tok: &PTok| -> Option<u32> {
let TokenKind::Int(lit) = &tok.kind else {
bad(pp, tok.range);
return None;
};
let n = u32::try_from(lit.value)
.ok()
.filter(|n| n.is_power_of_two() && *n <= 16);
if n.is_none() {
bad(pp, tok.range);
}
n
};
let next = match inner {
[] => Some(None),
[tok] if tok.name() == Some("pop") => match self.pack_stack.pop() {
Some(value) => Some(value),
None => {
self.diags
.error(range, "#pragma pack(pop) with nothing pushed");
return;
}
},
[tok] if tok.name() == Some("push") => {
self.pack_stack.push(self.pack);
Some(self.pack)
}
[tok] => value(self, tok).map(Some),
[push, comma, tok] if push.name() == Some("push") && comma.is_punct(Punct::Comma) => {
self.pack_stack.push(self.pack);
value(self, tok).map(Some)
}
_ => {
bad(self, range);
return;
}
};
let Some(next) = next else { return };
self.pack = next;
let at = self.out.len();
self.pack_events.push((at, next));
}
fn cur_key(&self) -> String {
self.cur().key.clone()
}
const OPTIONS: &'static str = "'target', 'include_path', 'system_include', 'link', \
'export', 'safe', 'no_std' and 'crate'";
fn cinrs_pragma(&mut self, rest: &[PTok], range: SourceRange) {
let Some(option) = rest.first() else {
self.diags.error(
range,
format!("#pragma cinrs needs an option: {}", Self::OPTIONS),
);
return;
};
let name = option.name().unwrap_or_default();
match name {
"target" => self.target_pragma(range),
"include_path" | "link" | "crate" => {
let Some(value) = self.pragma_string(&rest[1..], option.range, name) else {
return;
};
match name {
"include_path" => self.search.add_pragma(&value),
"link" => {
if !self.link_libraries.contains(&value) {
self.link_libraries.push(value);
}
}
_ => self.crate_pragma(value, rest[1].range),
}
}
"safe" => self.safe_pragma(&rest[1..], option.range),
"system_include" => self.system_include_pragma(&rest[1..], option.range),
"export" | "no_std" => {
if let Some(extra) = rest.get(1) {
self.diags.error(
extra.range,
format!(
"unexpected {} after #pragma cinrs {name}, which takes no argument",
extra.kind.describe()
),
);
}
if name == "export" {
self.export = true;
} else {
self.no_std = true;
}
}
other => {
let what = if other.is_empty() {
option.kind.describe().to_owned()
} else {
format!("'{other}'")
};
self.diags.error(
option.range,
format!(
"unknown #pragma cinrs option {what}; the options are {}",
Self::OPTIONS
),
);
}
}
}
fn target_pragma(&mut self, range: SourceRange) {
if !self.target_pragmas.scanned(range) {
let now = match self.target_source.triple() {
Some(triple) => format!("for '{triple}'"),
None => format!("for the model {} named", self.target_source.as_str()),
};
self.diags.error(
range,
format!(
"'#pragma cinrs target' is read before preprocessing, so it has to be a \
directive in the unit's own text: a header's comes too late, and one \
out of '_Pragma' is never seen. This unit is being translated {now}"
),
);
return;
}
if !self.target_pragmas.applied {
return;
}
if self.model_observed {
self.diags.error(
range,
"'#pragma cinrs target' must come before every '#include' and '#if', which \
were already answered with the previous data model",
);
}
}
fn safe_pragma(&mut self, rest: &[PTok], range: SourceRange) {
if rest.is_empty() {
self.diags.error(
range,
"#pragma cinrs safe needs the name of at least one function",
);
return;
}
for tok in rest {
match tok.name() {
Some(name) => self.safe_functions.push(SafeName {
name: name.to_owned(),
range: tok.range,
}),
None => self.diags.error(
tok.range,
format!(
"#pragma cinrs safe takes function names, found {}",
tok.kind.describe()
),
),
}
}
}
fn system_include_pragma(&mut self, rest: &[PTok], range: SourceRange) {
let mode = match rest.first() {
None => include::System::Last,
Some(tok) if tok.name() == Some("first") => include::System::First,
Some(tok) => {
let what = match tok.name() {
Some(name) => format!("'{name}'"),
None => tok.kind.describe().to_owned(),
};
self.diags.error(
tok.range,
format!(
"unexpected {what} after #pragma cinrs system_include, which takes \
either nothing or 'first'"
),
);
return;
}
};
if let Some(extra) = rest.get(1) {
self.diags.error(
extra.range,
format!(
"unexpected {} after #pragma cinrs system_include first",
extra.kind.describe()
),
);
}
self.model_observed = true;
self.enable_system_include(mode, range);
}
fn enable_system_include(&mut self, mode: include::System, range: SourceRange) {
match include::system_directories(&self.target, &self.target_source) {
Ok(dirs) => self.search.enable_system(mode, dirs),
Err(message) => self.diags.error(range, message),
}
}
fn crate_pragma(&mut self, path: String, range: SourceRange) {
if !crate::codegen::is_crate_path(&path) {
self.diags.error(
range,
format!(
"'{path}' is not usable as a Rust path to a crate; write something like \
'::my_cinrs' or 'crate::vendor::cinrs'"
),
);
return;
}
if let Some(previous) = &self.crate_path
&& *previous != path
{
self.diags.error(
range,
format!(
"this unit already reaches the cinrs crate as '{previous}', by an earlier \
#pragma cinrs crate"
),
);
return;
}
self.crate_path = Some(path);
}
fn pragma_string(&mut self, rest: &[PTok], range: SourceRange, option: &str) -> Option<String> {
let Some(tok) = rest.first() else {
self.diags.error(
range,
format!("#pragma cinrs {option} needs a string literal"),
);
return None;
};
let TokenKind::Str(lit) = &tok.kind else {
self.diags.error(
tok.range,
format!(
"#pragma cinrs {option} needs a string literal, found {}",
tok.kind.describe()
),
);
return None;
};
let Some(bytes) = lit.as_bytes() else {
self.diags.error(
tok.range,
format!("#pragma cinrs {option} does not take a wide string literal"),
);
return None;
};
let value = String::from_utf8_lossy(&bytes).into_owned();
if value.is_empty() {
self.diags.error(
tok.range,
format!("#pragma cinrs {option} was given an empty string"),
);
return None;
}
if let Some(extra) = rest.get(1) {
self.diags.error(
extra.range,
format!(
"unexpected {} after #pragma cinrs {option}",
extra.kind.describe()
),
);
}
Some(value)
}
fn include(&mut self, line: &[PTok], range: SourceRange, next: bool) {
self.model_observed = true;
let Some((name, form)) = self.header_name(line, range) else {
return;
};
if self.open.len() >= MAX_INCLUDE_DEPTH {
self.diags.error(
range,
format!("#include nested too deeply (more than {MAX_INCLUDE_DEPTH} files)"),
);
return;
}
let origin = self.cur().origin.clone();
let looked = if next {
let current = self.cur().found_in.clone();
include::resolve_next(&name, &origin, current.as_ref(), &self.search)
} else {
include::resolve(&name, form, &origin, &self.search)
};
let found = match looked {
Ok(found) => found,
Err(include::Error::Unreadable { path, error }) => {
self.diags
.error(range, format!("cannot read '{path}': {error}"));
return;
}
Err(include::Error::NotFound { searched }) => {
let quoted = match form {
include::Form::Angled => format!("<{name}>"),
include::Form::Quoted => format!("\"{name}\""),
};
let message = match (next, searched.is_empty()) {
(false, _) => {
format!("{quoted} file not found; searched: {}", searched.join(", "))
}
(true, true) => format!(
"{quoted} file not found by #include_next; there is nothing after the \
place this file was found in"
),
(true, false) => format!(
"{quoted} file not found by #include_next; searched: {}",
searched.join(", ")
),
};
self.diags.error(range, message);
return;
}
};
if self.once.contains(&found.key) {
return;
}
if let Some(guard) = self.guards.get(&found.key)
&& self.macros.contains_key(guard)
{
return;
}
if let Some(path) = &found.path
&& !self.user_headers.contains(path)
{
self.user_headers.push(path.clone());
}
self.open_file(found, range);
}
fn embed(&mut self, rest: &[PTok], range: SourceRange) {
let Some((name, form, after)) = self.embed_operand(rest, range) else {
return;
};
let Some(params) = self.embed_parameters(&rest[after..], range, true) else {
return;
};
let origin = self.cur().origin.clone();
let found = match include::resolve_embed(&name, form, &origin, &self.search) {
Ok(found) => found,
Err(include::Error::Unreadable { path, error }) => {
self.diags
.error(range, format!("cannot read '{path}': {error}"));
return;
}
Err(include::Error::NotFound { searched }) => {
let quoted = match form {
include::Form::Angled => format!("<{name}>"),
include::Form::Quoted => format!("\"{name}\""),
};
let where_ = if searched.is_empty() {
"there is nowhere to look; name a directory with \
`#pragma cinrs include_path`"
.to_owned()
} else {
format!("searched: {}", searched.join(", "))
};
self.diags.error(
range,
format!("{quoted} resource not found for #embed; {where_}"),
);
return;
}
};
if !self.embedded_files.contains(&found.path) {
self.embedded_files.push(found.path.clone());
}
let take = params.limit.unwrap_or(found.bytes.len());
let bytes = &found.bytes[..take.min(found.bytes.len())];
let mut out: Vec<PTok> = Vec::new();
if bytes.is_empty() {
out.extend(params.if_empty);
} else {
out.extend(params.prefix);
for (i, byte) in bytes.iter().enumerate() {
if i > 0 {
out.push(self.embed_token(TokenKind::Punct(Punct::Comma), range));
}
out.push(self.embed_token(int_token_kind(u128::from(*byte)), range));
}
out.extend(params.suffix);
}
self.push_pending(out, true);
}
fn embed_operand(
&mut self,
rest: &[PTok],
range: SourceRange,
) -> Option<(String, include::Form, usize)> {
if let Some(found) = self.embed_name_of(rest) {
return Some(found);
}
if !rest.is_empty() {
let expanded = self.expand_sequence(rest.to_vec());
if let Some((name, form, _)) = self.embed_name_of(&expanded) {
return Some((name, form, rest.len()));
}
}
self.diags
.error(range, "#embed expects \"RESOURCE\" or <RESOURCE>");
None
}
fn embed_name_of(&self, toks: &[PTok]) -> Option<(String, include::Form, usize)> {
match &toks.first()?.kind {
TokenKind::Str(lit) if lit.kind == lex::StrKind::Narrow => {
let spelling = lit.text.as_str();
let name = spelling
.strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
.unwrap_or(spelling);
(!name.is_empty()).then(|| (name.to_owned(), include::Form::Quoted, 1))
}
TokenKind::Punct(Punct::Lt) => {
let close = toks[1..].iter().position(|t| t.is_punct(Punct::Gt))? + 1;
let raw = self
.raw_text(toks[0].range.end, toks[close].range.start)
.trim()
.to_owned();
let name = if raw.is_empty() {
let mut spelled = String::new();
for (i, tok) in toks[1..close].iter().enumerate() {
if i > 0 && tok.space {
spelled.push(' ');
}
spelled.push_str(tok.spelling());
}
spelled
} else {
raw
};
(!name.is_empty()).then(|| (name, include::Form::Angled, close + 1))
}
_ => None,
}
}
fn embed_parameters(
&mut self,
mut rest: &[PTok],
range: SourceRange,
report: bool,
) -> Option<EmbedParams> {
let mut params = EmbedParams::default();
while let Some(first) = rest.first() {
let Some(name) = first.name() else {
if report {
self.diags.error(
first.range,
format!(
"expected an #embed parameter, found {}",
first.kind.describe()
),
);
}
return None;
};
if rest.get(1).is_none_or(|t| !t.is_punct(Punct::LParen)) {
if report {
self.diags.error(
first.range,
format!("#embed parameter '{name}' takes an argument list"),
);
}
return None;
}
let mut depth = 0usize;
let mut close = None;
for (i, tok) in rest[1..].iter().enumerate() {
if tok.is_punct(Punct::LParen) {
depth += 1;
} else if tok.is_punct(Punct::RParen) {
depth -= 1;
if depth == 0 {
close = Some(i + 1);
break;
}
}
}
let Some(close) = close else {
if report {
self.diags.error(
first.range,
format!("unterminated argument list for '{name}'"),
);
}
return None;
};
let inner = &rest[2..close];
let plain = name
.strip_prefix("__")
.and_then(|n| n.strip_suffix("__"))
.unwrap_or(name);
match plain {
"limit" => {
if inner.is_empty() {
if report {
self.diags
.error(first.range, "#embed 'limit' takes a constant expression");
}
return None;
}
let value = self.eval_expression(inner, range)?;
if value < 0 {
if report {
self.diags
.error(first.range, "#embed 'limit' cannot be negative");
}
return None;
}
params.limit = Some(usize::try_from(value).unwrap_or(usize::MAX));
}
"prefix" => params.prefix = inner.to_vec(),
"suffix" => params.suffix = inner.to_vec(),
"if_empty" => params.if_empty = inner.to_vec(),
_ => {
if report {
self.diags
.error(first.range, format!("unknown #embed parameter '{name}'"));
}
return None;
}
}
rest = &rest[close + 1..];
}
Some(params)
}
fn embed_token(&self, kind: TokenKind, range: SourceRange) -> PTok {
PTok {
kind,
range,
bol: false,
space: true,
origin: Origin::Source,
hide: HideSet::default(),
errors: Vec::new(),
}
}
fn open_file(&mut self, found: include::Resolved, directive: SourceRange) {
let base = self.next_base;
self.next_base = base
.saturating_add(found.text.len() as Pos)
.saturating_add(FILE_GAP);
let mut input: Vec<PTok> = lex::lex_text(&found.text, base, &self.lex_options)
.iter()
.map(PTok::from_lexed)
.collect();
if input.is_empty() {
input.push(eof_token(base));
}
if let Some(guard) = detect_include_guard(&input) {
self.guards.insert(found.key.clone(), guard);
}
self.included.push(IncludedFile {
name: found.name.clone(),
text: found.text.clone(),
base,
directive,
});
self.files
.push(FileEntry::new(found.text, base, 1, found.name));
self.open.push(OpenFile {
input,
pos: 0,
origin: found.origin,
found_in: found.found_in,
key: found.key,
cond_base: self.conds.len(),
});
}
fn header_name(
&mut self,
line: &[PTok],
range: SourceRange,
) -> Option<(String, include::Form)> {
let text = self.directive_text(line, 1);
if let Some(found) = parse_header_name(&text) {
return Some(found);
}
if line.len() <= 1 {
self.diags
.error(range, "#include expects \"FILENAME\" or <FILENAME>");
return None;
}
let expanded = self.expand_sequence(line[1..].to_vec());
let mut spelled = String::new();
for (i, tok) in expanded.iter().enumerate() {
if i > 0 && tok.space {
spelled.push(' ');
}
spelled.push_str(tok.spelling());
}
if let Some(found) = parse_header_name(&spelled) {
return Some(found);
}
self.diags.error(
range,
"#include expects \"FILENAME\" or <FILENAME>".to_owned(),
);
None
}
fn macro_name_operand(
&mut self,
rest: &[PTok],
range: SourceRange,
directive: &str,
) -> Option<String> {
let Some(first) = rest.first() else {
self.diags
.error(range, format!("no macro name given in #{directive}"));
return None;
};
let Some(name) = first.name() else {
self.diags.error(
first.range,
format!(
"macro name must be an identifier, found {}",
first.kind.describe()
),
);
return None;
};
if name == "defined" {
self.diags.error(
first.range,
format!("'defined' cannot be used as a macro name in #{directive}"),
);
return None;
}
Some(name.to_owned())
}
fn open_cond(&mut self, range: SourceRange, test: impl FnOnce(&mut Self) -> bool) {
let outer_active = !self.skipping();
let value = outer_active && test(self);
self.conds.push(Cond {
range,
outer_active,
taken: value,
active: outer_active && value,
seen_else: false,
});
}
fn elif(&mut self, range: SourceRange, directive: &str, test: impl FnOnce(&mut Self) -> bool) {
let Some(cond) = self.conds.last() else {
self.diags.error(range, format!("#{directive} without #if"));
return;
};
if cond.seen_else {
let seen = cond.range;
self.diags.push(
Diagnostic::error(range, format!("#{directive} after #else"))
.with_note_at(seen, "the conditional started"),
);
return;
}
let (outer_active, taken) = (cond.outer_active, cond.taken);
let value = outer_active && !taken && test(self);
let cond = self
.conds
.last_mut()
.expect("the stack was not touched in between");
cond.active = value;
cond.taken |= value;
}
fn require_standard(&mut self, needed: Standard, what: &str, range: SourceRange) {
if let Some(message) = self.gating.requires(what, needed) {
self.diags.error(range, message);
}
}
fn else_(&mut self, rest: &[PTok], range: SourceRange) {
let Some(cond) = self.conds.last_mut() else {
self.diags.error(range, "#else without #if");
return;
};
if cond.seen_else {
let seen = cond.range;
self.diags.push(
Diagnostic::error(range, "#else after #else")
.with_note_at(seen, "the conditional started"),
);
return;
}
cond.seen_else = true;
cond.active = cond.outer_active && !cond.taken;
cond.taken = true;
let active = cond.active;
if active && !rest.is_empty() {
self.diags
.warning(range, "extra tokens at the end of #else");
}
}
fn endif(&mut self, range: SourceRange) {
if self.conds.pop().is_none() {
self.diags.error(range, "#endif without #if");
}
}
fn undef(&mut self, rest: &[PTok], range: SourceRange) {
if let Some(name) = self.macro_name_operand(rest, range, "undef") {
self.macros.remove(&name);
}
}
fn define(&mut self, rest: &[PTok], range: SourceRange) {
let Some(name_tok) = rest.first() else {
self.diags.error(range, "no macro name given in #define");
return;
};
let Some(name) = name_tok.name().map(str::to_owned) else {
self.diags.error(
name_tok.range,
format!(
"macro name must be an identifier, found {}",
name_tok.kind.describe()
),
);
return;
};
if name == "defined" {
self.diags
.error(name_tok.range, "'defined' cannot be used as a macro name");
return;
}
if name == VA_ARGS {
self.diags.error(
name_tok.range,
"'__VA_ARGS__' can only appear in the replacement list of a variadic macro",
);
return;
}
let mut rest = &rest[1..];
let function_like = rest
.first()
.is_some_and(|t| t.is_punct(Punct::LParen) && !t.space);
let (params, variadic, va_name) = if function_like {
let Some(parsed) = self.parse_params(rest, range) else {
return;
};
rest = &rest[parsed.used..];
(Some(parsed.params), parsed.variadic, parsed.va_name)
} else {
(None, false, None)
};
let body = fuse_hash_hash(rest);
let def = MacroDef {
params,
variadic,
va_name,
body,
name_range: name_tok.range,
predefined: false,
builtin: None,
};
if !self.check_body(&def, &name, range) {
return;
}
if let Some(previous) = self.macros.get(&name)
&& !previous.predefined
&& !previous.same_as(&def)
{
self.diags.push(
Diagnostic::error(name_tok.range, format!("macro '{name}' redefined"))
.with_note_at(
previous.name_range,
format!("previous definition of '{name}' is"),
),
);
return;
}
self.macros.insert(name, Arc::new(def));
}
fn parse_params(&mut self, rest: &[PTok], range: SourceRange) -> Option<ParamList> {
let mut params: Vec<String> = Vec::new();
let mut variadic = false;
let mut va_name = None;
let mut i = 1; if rest.get(i).is_some_and(|t| t.is_punct(Punct::RParen)) {
return Some(ParamList {
params,
variadic,
va_name,
used: i + 1,
});
}
loop {
let Some(tok) = rest.get(i) else {
self.diags
.error(range, "missing ')' in the parameter list of a macro");
return None;
};
if tok.is_punct(Punct::Ellipsis) {
self.require_standard(Standard::C99, "a variadic macro", tok.range);
variadic = true;
i += 1;
break;
}
let Some(name) = tok.name() else {
self.diags.error(
tok.range,
format!(
"expected a macro parameter name, found {}",
tok.kind.describe()
),
);
return None;
};
if name == VA_ARGS {
self.diags.error(
tok.range,
"'__VA_ARGS__' cannot be used as a macro parameter name",
);
return None;
}
if params.iter().any(|p| p == name) {
self.diags
.error(tok.range, format!("duplicate macro parameter '{name}'"));
return None;
}
if rest.get(i + 1).is_some_and(|t| t.is_punct(Punct::Ellipsis)) {
self.require_standard(Standard::C99, "a variadic macro", tok.range);
variadic = true;
va_name = Some(name.to_owned());
i += 2;
break;
}
params.push(name.to_owned());
i += 1;
match rest.get(i) {
Some(t) if t.is_punct(Punct::Comma) => i += 1,
Some(t) if t.is_punct(Punct::RParen) => break,
Some(t) => {
self.diags.error(
t.range,
format!(
"expected ',' or ')' in a macro parameter list, found {}",
t.kind.describe()
),
);
return None;
}
None => {
self.diags
.error(range, "missing ')' in the parameter list of a macro");
return None;
}
}
}
match rest.get(i) {
Some(t) if t.is_punct(Punct::RParen) => Some(ParamList {
params,
variadic,
va_name,
used: i + 1,
}),
_ => {
self.diags
.error(range, "missing ')' in the parameter list of a macro");
None
}
}
}
fn check_body(&mut self, def: &MacroDef, name: &str, range: SourceRange) -> bool {
let body = &def.body;
if let Some(first) = body.first()
&& first.is_punct(Punct::HashHash)
{
self.diags.error(
first.range,
"'##' cannot appear at the start of a macro replacement list",
);
return false;
}
if let Some(last) = body.last()
&& last.is_punct(Punct::HashHash)
&& body.len() > 1
{
self.diags.error(
last.range,
"'##' cannot appear at the end of a macro replacement list",
);
return false;
}
for (i, tok) in body.iter().enumerate() {
if def.params.is_some() && tok.is_punct(Punct::Hash) {
let ok = body
.get(i + 1)
.and_then(PTok::name)
.is_some_and(|n| def.param_index(n).is_some());
if !ok {
self.diags
.error(tok.range, "'#' must be followed by a macro parameter");
return false;
}
}
if tok.name() == Some(VA_ARGS) && def.param_index(VA_ARGS).is_none() {
self.diags.error(
tok.range,
"'__VA_ARGS__' can only appear in the replacement list of a variadic macro",
);
return false;
}
if tok.name() == Some(VA_OPT) && !self.check_va_opt(def, body, i) {
return false;
}
}
let _ = (name, range);
true
}
fn check_va_opt(&mut self, def: &MacroDef, body: &[PTok], at: usize) -> bool {
let tok = &body[at];
if def.param_index(VA_ARGS).is_none() {
self.diags.error(
tok.range,
"'__VA_OPT__' can only appear in the replacement list of a variadic macro",
);
return false;
}
self.require_standard(Standard::C23, "'__VA_OPT__'", tok.range);
if !body.get(at + 1).is_some_and(|t| t.is_punct(Punct::LParen)) {
self.diags
.error(tok.range, "'__VA_OPT__' must be followed by '('");
return false;
}
let mut depth = 0i32;
let mut contents: Vec<&PTok> = Vec::new();
for tok in &body[at + 1..] {
if tok.is_punct(Punct::LParen) {
depth += 1;
if depth == 1 {
continue;
}
} else if tok.is_punct(Punct::RParen) {
depth -= 1;
if depth == 0 {
return self.check_va_opt_contents(&contents);
}
} else if tok.name() == Some(VA_OPT) {
self.diags
.error(tok.range, "'__VA_OPT__' cannot be nested inside another");
return false;
}
contents.push(tok);
}
self.diags.error(
tok.range,
"unterminated '__VA_OPT__(' in a macro definition",
);
false
}
fn check_va_opt_contents(&mut self, contents: &[&PTok]) -> bool {
for (end, tok) in [("start", contents.first()), ("end", contents.last())] {
if let Some(tok) = tok
&& tok.is_punct(Punct::HashHash)
{
self.diags.error(
tok.range,
format!("'##' cannot appear at the {end} of a '__VA_OPT__' argument"),
);
return false;
}
}
true
}
}
fn expand_va_opt(def: &MacroDef, args: &Args) -> Option<Vec<PTok>> {
let params = def.params.as_ref()?;
if !def.variadic || !def.body.iter().any(|t| t.name() == Some(VA_OPT)) {
return None;
}
let present = !args.get(params.len()).is_empty();
let body = &def.body;
let mut out: Vec<PTok> = Vec::with_capacity(body.len());
let mut i = 0;
while i < body.len() {
let is_va_opt = body[i].name() == Some(VA_OPT)
&& body.get(i + 1).is_some_and(|t| t.is_punct(Punct::LParen));
if !is_va_opt {
out.push(body[i].clone());
i += 1;
continue;
}
let space = body[i].space;
let mut depth = 0i32;
let mut inner: Vec<PTok> = Vec::new();
let mut j = i + 1;
while j < body.len() {
let tok = &body[j];
j += 1;
if tok.is_punct(Punct::LParen) {
depth += 1;
if depth == 1 {
continue;
}
} else if tok.is_punct(Punct::RParen) {
depth -= 1;
if depth == 0 {
break;
}
}
inner.push(tok.clone());
}
if present {
if let Some(first) = inner.first_mut() {
first.space = space;
}
out.extend(inner);
}
i = j;
}
Some(out)
}
fn parse_header_name(text: &str) -> Option<(String, include::Form)> {
let text = text.trim();
let (form, close) = match text.as_bytes().first()? {
b'<' => (include::Form::Angled, '>'),
b'"' => (include::Form::Quoted, '"'),
_ => return None,
};
let rest = &text[1..];
let end = rest.find(close)?;
let name = &rest[..end];
(!name.is_empty()).then(|| (name.to_owned(), form))
}
fn detect_include_guard(toks: &[PTok]) -> Option<String> {
if !(toks.first()?.bol && toks[0].is_punct(Punct::Hash)) {
return None;
}
if toks.get(1)?.name()? != "ifndef" {
return None;
}
let name = toks.get(2)?.name()?.to_owned();
let after_ifndef = toks.get(3)?;
if !after_ifndef.bol && !after_ifndef.is_eof() {
return None;
}
if !after_ifndef.is_punct(Punct::Hash)
|| toks.get(4)?.name()? != "define"
|| toks.get(5)?.name()? != name
{
return None;
}
let mut depth = 0i32;
let mut i = 0;
while i < toks.len() && !toks[i].is_eof() {
if toks[i].bol && toks[i].is_punct(Punct::Hash) {
match toks.get(i + 1).and_then(PTok::name) {
Some("if" | "ifdef" | "ifndef") => depth += 1,
Some("endif") => {
depth -= 1;
if depth == 0 {
let mut j = i + 2;
while toks.get(j).is_some_and(|t| !t.bol && !t.is_eof()) {
j += 1;
}
return toks.get(j).is_none_or(PTok::is_eof).then_some(name);
}
}
_ => {}
}
}
i += 1;
}
None
}
fn fuse_hash_hash(rest: &[PTok]) -> Vec<PTok> {
let mut body: Vec<PTok> = Vec::with_capacity(rest.len());
let mut i = 0;
while i < rest.len() {
if rest[i].is_punct(Punct::Hash)
&& let Some(next) = rest.get(i + 1)
&& next.is_punct(Punct::Hash)
&& !next.bol
{
let mut fused = rest[i].clone();
fused.kind = TokenKind::Punct(Punct::HashHash);
fused.range = fused.range.join(next.range);
body.push(fused);
i += 2;
continue;
}
body.push(rest[i].clone());
i += 1;
}
if let Some(first) = body.first_mut() {
first.space = false;
}
body
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct Val {
v: i128,
unsigned: bool,
}
impl Val {
fn signed(v: i128) -> Val {
Val {
v: v as i64 as i128,
unsigned: false,
}
}
fn make(v: i128, unsigned: bool) -> Val {
if unsigned {
Val {
v: (v as u64) as i128,
unsigned,
}
} else {
Val::signed(v)
}
}
fn boolean(b: bool) -> Val {
Val::signed(i128::from(b))
}
fn is_true(self) -> bool {
self.v != 0
}
fn as_operand(self, unsigned: bool) -> i128 {
if unsigned && self.v < 0 {
self.v + (1i128 << 64)
} else {
self.v
}
}
}
impl Pp<'_> {
fn eval_condition(&mut self, line: &[PTok], range: SourceRange) -> bool {
self.model_observed = true;
if line.is_empty() {
self.diags.error(range, "#if with no expression");
return false;
}
self.eval_expression(line, range)
.is_some_and(|value| value != 0)
}
fn eval_expression(&mut self, line: &[PTok], range: SourceRange) -> Option<i128> {
let prepared = self.resolve_defined(line, range)?;
let expanded = self.expand_sequence(prepared);
for tok in &expanded {
let tok = tok.clone();
self.report_errors(&tok);
}
let mut eval = Eval {
toks: &expanded,
pos: 0,
fallback: range,
errors: Vec::new(),
depth: 0,
};
let value = eval.expression(true);
if eval.errors.is_empty()
&& eval.pos < eval.toks.len()
&& let Some(tok) = eval.toks.get(eval.pos)
{
let found = tok.kind.describe();
eval.errors.push(Diagnostic::error(
tok.range,
format!("unexpected {found} in a preprocessor expression"),
));
}
let failed = !eval.errors.is_empty();
for diag in eval.errors {
self.diags.push(diag);
}
(!failed).then_some(value.v)
}
fn resolve_defined(&mut self, line: &[PTok], range: SourceRange) -> Option<Vec<PTok>> {
let mut out = Vec::with_capacity(line.len());
let mut i = 0;
while i < line.len() {
let tok = &line[i];
if let Some(name) = tok.name()
&& name.starts_with("__has_")
{
let (value, end) = self.has_operator(name, line, i)?;
let mut answer = tok.clone();
answer.kind = int_token_kind(value);
answer.hide = answer.hide.add(name);
answer.errors.clear();
out.push(answer);
i = end;
continue;
}
if tok.name() != Some("defined") {
out.push(tok.clone());
i += 1;
continue;
}
let parenthesised = line.get(i + 1).is_some_and(|t| t.is_punct(Punct::LParen));
let name_at = if parenthesised { i + 2 } else { i + 1 };
let Some(name) = line.get(name_at).and_then(PTok::name) else {
self.diags.error(
tok.range,
"operator 'defined' requires an identifier as its operand",
);
return None;
};
let defined = self.macros.contains_key(name);
let mut end = name_at + 1;
if parenthesised {
if !line.get(end).is_some_and(|t| t.is_punct(Punct::RParen)) {
self.diags.error(range, "missing ')' after 'defined'");
return None;
}
end += 1;
}
let mut value = tok.clone();
value.kind = TokenKind::Int(IntLit {
value: u128::from(defined),
base: NumBase::Decimal,
unsigned: false,
long: LongKind::None,
text: u8::from(defined).to_string(),
});
value.hide = value.hide.add("defined");
value.errors.clear();
out.push(value);
i = end;
}
Some(out)
}
fn has_operator(&mut self, name: &str, line: &[PTok], at: usize) -> Option<(u128, usize)> {
let range = line[at].range;
if !line.get(at + 1).is_some_and(|t| t.is_punct(Punct::LParen)) {
return Some((0, at + 1));
}
let mut depth = 0i32;
let mut end = at + 1;
while end < line.len() {
if line[end].is_punct(Punct::LParen) {
depth += 1;
} else if line[end].is_punct(Punct::RParen) {
depth -= 1;
if depth == 0 {
end += 1;
break;
}
}
end += 1;
}
if depth != 0 {
self.diags
.error(range, format!("missing ')' after '{name}'"));
return None;
}
let inner = &line[at + 2..end - 1];
let value = match name {
"__has_include" | "__has_include_next" => {
let Some((header, form)) = self.operand_header_name(inner) else {
self.diags.error(
range,
format!("'{name}' expects \"FILENAME\" or <FILENAME>"),
);
return None;
};
let origin = self.cur().origin.clone();
let found = if name == "__has_include_next" {
let current = self.cur().found_in.clone();
include::resolve_next(&header, &origin, current.as_ref(), &self.search)
} else {
include::resolve(&header, form, &origin, &self.search)
};
u128::from(found.is_ok())
}
"__has_attribute" | "__has_declspec_attribute" => u128::from(
inner
.first()
.and_then(PTok::name)
.is_some_and(crate::gnu::has_attribute),
),
"__has_c_attribute" => inner
.first()
.and_then(PTok::name)
.map_or(0, |n| u128::from(crate::gnu::has_c_attribute(n))),
"__has_builtin" => u128::from(
inner
.first()
.and_then(PTok::name)
.is_some_and(crate::gnu::has_builtin),
),
"__has_feature" | "__has_extension" => u128::from(
inner
.first()
.and_then(PTok::name)
.is_some_and(crate::gnu::has_feature),
),
"__has_embed" => {
let Some((resource, form, after)) = self.embed_name_of(inner) else {
self.diags.error(
range,
format!("'{name}' expects \"RESOURCE\" or <RESOURCE>"),
);
return None;
};
let params = self.embed_parameters(&inner[after..], range, false);
let origin = self.cur().origin.clone();
match (
params,
include::resolve_embed(&resource, form, &origin, &self.search),
) {
(Some(params), Ok(found)) => {
let take = params.limit.unwrap_or(found.bytes.len());
if take == 0 || found.bytes.is_empty() {
EMBED_EMPTY
} else {
EMBED_FOUND
}
}
_ => EMBED_NOT_FOUND,
}
}
_ => 0,
};
Some((value, end))
}
fn operand_header_name(&mut self, inner: &[PTok]) -> Option<(String, include::Form)> {
let first = inner.first()?;
let last = inner.last()?;
let text = self.raw_text(first.range.start, last.range.end).trim();
if let Some(found) = parse_header_name(text) {
return Some(found);
}
let expanded = self.expand_sequence(inner.to_vec());
let mut spelled = String::new();
for (i, tok) in expanded.iter().enumerate() {
if i > 0 && tok.space {
spelled.push(' ');
}
spelled.push_str(tok.spelling());
}
parse_header_name(&spelled)
}
}
struct Eval<'a> {
toks: &'a [PTok],
pos: usize,
fallback: SourceRange,
errors: Vec<Diagnostic>,
depth: u32,
}
const MAX_EVAL_DEPTH: u32 = 200;
impl Eval<'_> {
fn peek(&self) -> Option<&PTok> {
self.toks.get(self.pos)
}
fn at(&self, p: Punct) -> bool {
self.peek().is_some_and(|t| t.is_punct(p))
}
fn eat(&mut self, p: Punct) -> bool {
if self.at(p) {
self.pos += 1;
return true;
}
false
}
fn range(&self) -> SourceRange {
self.peek().map_or(self.fallback, |t| t.range)
}
fn error(&mut self, range: SourceRange, message: impl Into<String>) {
self.errors.push(Diagnostic::error(range, message));
}
fn expression(&mut self, eval: bool) -> Val {
self.depth += 1;
if self.depth > MAX_EVAL_DEPTH {
let range = self.range();
self.error(range, "this preprocessor expression nests too deeply");
self.pos = self.toks.len();
self.depth -= 1;
return Val::signed(0);
}
let mut value = self.conditional(eval);
while self.eat(Punct::Comma) {
value = self.conditional(eval);
}
self.depth -= 1;
value
}
fn conditional(&mut self, eval: bool) -> Val {
let cond = self.binary(0, eval);
if !self.eat(Punct::Question) {
return cond;
}
let take_then = cond.is_true();
let then_value = self.expression(eval && take_then);
if !self.eat(Punct::Colon) {
let range = self.range();
self.error(range, "expected ':' in a preprocessor expression");
return cond;
}
let else_value = self.conditional(eval && !take_then);
let (a, b) = (then_value, else_value);
let unsigned = a.unsigned || b.unsigned;
let picked = if take_then { a } else { b };
Val::make(picked.as_operand(unsigned), unsigned)
}
fn binary(&mut self, min_prec: u8, eval: bool) -> Val {
let mut lhs = self.unary(eval);
loop {
let Some((op, prec)) = self.peek().and_then(|t| binary_op(&t.kind)) else {
return lhs;
};
if prec < min_prec {
return lhs;
}
let op_range = self.range();
self.pos += 1;
let rhs_eval = match op {
BinOp::LogAnd => eval && lhs.is_true(),
BinOp::LogOr => eval && !lhs.is_true(),
_ => eval,
};
let rhs = self.binary(prec + 1, rhs_eval);
lhs = self.apply(op, lhs, rhs, op_range, eval);
}
}
fn apply(&mut self, op: BinOp, a: Val, b: Val, range: SourceRange, eval: bool) -> Val {
use BinOp::*;
if op == LogAnd {
return Val::boolean(a.is_true() && b.is_true());
}
if op == LogOr {
return Val::boolean(a.is_true() || b.is_true());
}
let unsigned = a.unsigned || b.unsigned;
let (x, y) = (a.as_operand(unsigned), b.as_operand(unsigned));
match op {
Eq => Val::boolean(x == y),
Ne => Val::boolean(x != y),
Lt => Val::boolean(x < y),
Gt => Val::boolean(x > y),
Le => Val::boolean(x <= y),
Ge => Val::boolean(x >= y),
Add => Val::make(x.wrapping_add(y), unsigned),
Sub => Val::make(x.wrapping_sub(y), unsigned),
Mul => Val::make(x.wrapping_mul(y), unsigned),
Div | Rem => {
if y == 0 {
if eval {
self.error(range, "division by zero in a preprocessor expression");
}
return Val::make(0, unsigned);
}
let v = if op == Div {
x.wrapping_div(y)
} else {
x.wrapping_rem(y)
};
Val::make(v, unsigned)
}
BitAnd => Val::make(x & y, unsigned),
BitOr => Val::make(x | y, unsigned),
BitXor => Val::make(x ^ y, unsigned),
Shl => Val::make(x.wrapping_shl((y as u64 & 63) as u32), unsigned),
Shr => {
let count = (y as u64 & 63) as u32;
if unsigned {
Val::make(((x as u64) >> count) as i128, unsigned)
} else {
Val::make((x as i64 >> count) as i128, unsigned)
}
}
LogAnd | LogOr => unreachable!("handled above"),
}
}
fn unary(&mut self, eval: bool) -> Val {
let range = self.range();
if self.eat(Punct::Plus) {
return self.unary(eval);
}
if self.eat(Punct::Minus) {
let v = self.unary(eval);
return Val::make(v.as_operand(v.unsigned).wrapping_neg(), v.unsigned);
}
if self.eat(Punct::Tilde) {
let v = self.unary(eval);
return Val::make(!v.as_operand(v.unsigned), v.unsigned);
}
if self.eat(Punct::Bang) {
let v = self.unary(eval);
return Val::boolean(!v.is_true());
}
if self.eat(Punct::LParen) {
let v = self.expression(eval);
if !self.eat(Punct::RParen) {
let at = self.range();
self.error(at, "expected ')' in a preprocessor expression");
}
return v;
}
self.primary(range)
}
fn primary(&mut self, range: SourceRange) -> Val {
let Some(tok) = self.peek() else {
self.error(range, "expected a value in a preprocessor expression");
return Val::signed(0);
};
let value = match &tok.kind {
TokenKind::Int(lit) => {
let unsigned = lit.unsigned || lit.value > i64::MAX as u128;
let too_large = lit.value > u64::MAX as u128;
let value = Val::make((lit.value & u128::from(u64::MAX)) as i128, unsigned);
if too_large {
let range = tok.range;
self.error(
range,
"integer constant is too large for a preprocessor expression",
);
}
value
}
TokenKind::Char(lit) => Val::signed(i128::from(lit.value)),
TokenKind::Float(_) => {
let range = tok.range;
self.error(
range,
"a floating constant is not allowed in a preprocessor expression",
);
Val::signed(0)
}
TokenKind::Str(_) => {
let range = tok.range;
self.error(
range,
"a string literal is not allowed in a preprocessor expression",
);
Val::signed(0)
}
TokenKind::Keyword(lex::Keyword::True) => Val::signed(1),
TokenKind::Keyword(lex::Keyword::False) => Val::signed(0),
TokenKind::Ident(_) | TokenKind::Keyword(_) => Val::signed(0),
other => {
let range = tok.range;
let found = other.describe();
self.error(
range,
format!("unexpected {found} in a preprocessor expression"),
);
Val::signed(0)
}
};
self.pos += 1;
value
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum BinOp {
LogOr,
LogAnd,
BitOr,
BitXor,
BitAnd,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
Shl,
Shr,
Add,
Sub,
Mul,
Div,
Rem,
}
fn binary_op(kind: &TokenKind) -> Option<(BinOp, u8)> {
let TokenKind::Punct(p) = kind else {
return None;
};
Some(match p {
Punct::PipePipe => (BinOp::LogOr, 1),
Punct::AmpAmp => (BinOp::LogAnd, 2),
Punct::Pipe => (BinOp::BitOr, 3),
Punct::Caret => (BinOp::BitXor, 4),
Punct::Amp => (BinOp::BitAnd, 5),
Punct::EqEq => (BinOp::Eq, 6),
Punct::Ne => (BinOp::Ne, 6),
Punct::Lt => (BinOp::Lt, 7),
Punct::Gt => (BinOp::Gt, 7),
Punct::Le => (BinOp::Le, 7),
Punct::Ge => (BinOp::Ge, 7),
Punct::Shl => (BinOp::Shl, 8),
Punct::Shr => (BinOp::Shr, 8),
Punct::Plus => (BinOp::Add, 9),
Punct::Minus => (BinOp::Sub, 9),
Punct::Star => (BinOp::Mul, 10),
Punct::Slash => (BinOp::Div, 10),
Punct::Percent => (BinOp::Rem, 10),
_ => return None,
})
}
impl Standard {
pub fn stdc_version(self) -> Option<&'static str> {
Some(match self {
Standard::C89 => return None,
Standard::C99 => "199901L",
Standard::C11 => "201112L",
Standard::C17 => "201710L",
Standard::C23 => "202311L",
})
}
}
impl Pp<'_> {
fn define_predefined(&mut self, options: &Options) {
self.define_object("__STDC__", "1");
self.define_object("__STDC_HOSTED__", "1");
if let Some(version) = options.standard.stdc_version() {
self.define_object("__STDC_VERSION__", version);
}
self.define_object("__cinrs__", "1");
if !options.complex {
self.define_object("__STDC_NO_COMPLEX__", "1");
}
if !threads_available(&options.target) {
self.define_object("__STDC_NO_THREADS__", "1");
}
self.define_atomic_macros(options.target.max_scalar_align.min(8));
self.define_object("__STDC_UTF_16__", "1");
self.define_object("__STDC_UTF_32__", "1");
self.define_object("__STDC_EMBED_NOT_FOUND__", "0");
self.define_object("__STDC_EMBED_FOUND__", "1");
self.define_object("__STDC_EMBED_EMPTY__", "2");
if !options.dialect.is_gnu() {
self.define_object("__STRICT_ANSI__", "1");
}
self.define_object("__GNUC__", "4");
self.define_object("__GNUC_MINOR__", "2");
self.define_object("__GNUC_PATCHLEVEL__", "1");
self.define_string(
"__VERSION__",
&format!("cinrs {}", env!("CARGO_PKG_VERSION")),
);
self.define_string("__DATE__", "??? ?? ????");
self.define_string("__TIME__", "??:??:??");
self.define_string("__TIMESTAMP__", "??? ??? ?? ??:??:?? ????");
let base_file = self.base_file.clone();
self.define_string("__BASE_FILE__", &base_file);
self.define_builtin("__LINE__", Builtin::Line);
self.define_builtin("__FILE__", Builtin::File);
self.define_builtin("__FILE_NAME__", Builtin::FileName);
self.define_builtin("__INCLUDE_LEVEL__", Builtin::IncludeLevel);
self.define_builtin("__COUNTER__", Builtin::Counter);
self.define_function("__builtin_LINE", "__LINE__");
self.define_function("__builtin_FILE", "__FILE__");
self.define_function("__builtin_FUNCTION", "__func__");
for (name, value) in target_macros(&options.target) {
self.define_object(name, &value);
}
}
fn define_atomic_macros(&mut self, max_atomic: u64) {
for (name, value) in [
("__ATOMIC_RELAXED", "0"),
("__ATOMIC_CONSUME", "1"),
("__ATOMIC_ACQUIRE", "2"),
("__ATOMIC_RELEASE", "3"),
("__ATOMIC_ACQ_REL", "4"),
("__ATOMIC_SEQ_CST", "5"),
] {
self.define_object(name, value);
}
for name in [
"__GCC_ATOMIC_BOOL_LOCK_FREE",
"__GCC_ATOMIC_CHAR_LOCK_FREE",
"__GCC_ATOMIC_CHAR8_T_LOCK_FREE",
"__GCC_ATOMIC_CHAR16_T_LOCK_FREE",
"__GCC_ATOMIC_CHAR32_T_LOCK_FREE",
"__GCC_ATOMIC_WCHAR_T_LOCK_FREE",
"__GCC_ATOMIC_SHORT_LOCK_FREE",
"__GCC_ATOMIC_INT_LOCK_FREE",
"__GCC_ATOMIC_LONG_LOCK_FREE",
"__GCC_ATOMIC_LLONG_LOCK_FREE",
"__GCC_ATOMIC_POINTER_LOCK_FREE",
] {
self.define_object(name, "2");
}
self.define_object("__GCC_ATOMIC_TEST_AND_SET_TRUEVAL", "1");
for width in [1u64, 2, 4, 8] {
if width <= max_atomic {
self.define_object(
match width {
1 => "__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1",
2 => "__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2",
4 => "__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4",
_ => "__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8",
},
"1",
);
}
}
}
fn define_function(&mut self, name: &str, body: &str) {
let tokens = lex::lex_text(body, self.base, &self.lex_options);
let body: Vec<PTok> = tokens
.iter()
.filter(|t| !matches!(t.kind, TokenKind::Eof))
.map(PTok::from_lexed)
.collect();
self.macros.insert(
name.to_owned(),
Arc::new(MacroDef {
params: Some(Vec::new()),
variadic: false,
va_name: None,
body,
name_range: SourceRange::at(self.base),
predefined: true,
builtin: None,
}),
);
}
fn define_object(&mut self, name: &str, body: &str) {
let tokens = lex::lex_text(body, self.base, &self.lex_options);
let body: Vec<PTok> = tokens
.iter()
.filter(|t| !matches!(t.kind, TokenKind::Eof))
.map(PTok::from_lexed)
.collect();
self.insert_predefined(name, body, None);
}
fn define_string(&mut self, name: &str, value: &str) {
let kind = string_token_kind(value);
let body = vec![PTok {
kind,
range: SourceRange::at(self.base),
bol: false,
space: false,
origin: Origin::Source,
hide: HideSet::default(),
errors: Vec::new(),
}];
self.insert_predefined(name, body, None);
}
fn define_builtin(&mut self, name: &str, builtin: Builtin) {
self.insert_predefined(name, Vec::new(), Some(builtin));
}
fn insert_predefined(&mut self, name: &str, body: Vec<PTok>, builtin: Option<Builtin>) {
self.macros.insert(
name.to_owned(),
Arc::new(MacroDef {
params: None,
variadic: false,
va_name: None,
body,
name_range: SourceRange::at(self.base),
predefined: true,
builtin,
}),
);
}
}
fn threads_available(target: &TargetModel) -> bool {
target.os == Os::Linux && matches!(target.env, Env::Gnu | Env::Musl)
}
fn target_macros(target: &TargetModel) -> Vec<(&'static str, String)> {
let mut out: Vec<(&'static str, String)> = target.macros();
let flag = |out: &mut Vec<(&'static str, String)>, name: &'static str| {
out.push((name, "1".to_owned()))
};
if target.ptr_bits == 64 && target.long_bits == 64 {
flag(&mut out, "__LP64__");
flag(&mut out, "_LP64");
} else if target.ptr_bits == 32 && target.int_bits == 32 && target.long_bits == 32 {
flag(&mut out, "__ILP32__");
flag(&mut out, "_ILP32");
}
if !target.char_signed {
flag(&mut out, "__CHAR_UNSIGNED__");
}
out.push(("__CHAR_BIT__", "8".to_owned()));
out.push(("__SIZEOF_SHORT__", (target.short_bits / 8).to_string()));
out.push(("__SIZEOF_INT__", (target.int_bits / 8).to_string()));
out.push(("__SIZEOF_LONG__", (target.long_bits / 8).to_string()));
out.push((
"__SIZEOF_LONG_LONG__",
(target.long_long_bits / 8).to_string(),
));
out.push(("__SIZEOF_POINTER__", (target.ptr_bits / 8).to_string()));
if target.has_int128 {
out.push(("__SIZEOF_INT128__", "16".to_owned()));
}
out.push(("__ORDER_LITTLE_ENDIAN__", "1234".to_owned()));
out.push(("__ORDER_BIG_ENDIAN__", "4321".to_owned()));
out.push((
"__BYTE_ORDER__",
if target.big_endian {
"4321".to_owned()
} else {
"1234".to_owned()
},
));
limit_macros(target, &mut out);
out
}
fn signed_max(bits: u32) -> String {
((1u128 << (bits - 1)) - 1).to_string()
}
fn unsigned_max(bits: u32) -> String {
(u128::MAX >> (128 - bits)).to_string()
}
fn limit_macros(target: &TargetModel, out: &mut Vec<(&'static str, String)>) {
let int_bits = target.int_bits;
let long_bits = target.long_bits;
let llong_bits = target.long_long_bits;
let ptr_bits = target.ptr_bits;
let (ptr_signed, ptr_unsigned, ptr_suffix) = if int_bits >= ptr_bits {
("int", "unsigned int", "")
} else if long_bits >= ptr_bits {
("long int", "long unsigned int", "L")
} else {
("long long int", "long long unsigned int", "LL")
};
let (max_signed, max_unsigned, max_suffix) = if long_bits >= llong_bits {
("long int", "long unsigned int", "L")
} else {
("long long int", "long long unsigned int", "LL")
};
let max_bits = long_bits.max(llong_bits);
let mut push = |name: &'static str, value: String| out.push((name, value));
push("__SCHAR_MAX__", signed_max(8));
push("__SHRT_MAX__", signed_max(target.short_bits));
push("__INT_MAX__", signed_max(int_bits));
push("__LONG_MAX__", format!("{}L", signed_max(long_bits)));
push("__LONG_LONG_MAX__", format!("{}LL", signed_max(llong_bits)));
push("__BOOL_WIDTH__", "1".to_owned());
push("__SCHAR_WIDTH__", "8".to_owned());
push("__SHRT_WIDTH__", target.short_bits.to_string());
push("__INT_WIDTH__", int_bits.to_string());
push("__LONG_WIDTH__", long_bits.to_string());
push("__LONG_LONG_WIDTH__", llong_bits.to_string());
push("__LLONG_WIDTH__", llong_bits.to_string());
push("__SIZE_TYPE__", ptr_unsigned.to_owned());
push(
"__SIZE_MAX__",
format!("{}U{ptr_suffix}", unsigned_max(ptr_bits)),
);
push("__SIZE_WIDTH__", ptr_bits.to_string());
push("__SIZEOF_SIZE_T__", (ptr_bits / 8).to_string());
push("__PTRDIFF_TYPE__", ptr_signed.to_owned());
push(
"__PTRDIFF_MAX__",
format!("{}{ptr_suffix}", signed_max(ptr_bits)),
);
push("__PTRDIFF_WIDTH__", ptr_bits.to_string());
push("__SIZEOF_PTRDIFF_T__", (ptr_bits / 8).to_string());
push("__INTMAX_TYPE__", max_signed.to_owned());
push(
"__INTMAX_MAX__",
format!("{}{max_suffix}", signed_max(max_bits)),
);
push("__INTMAX_WIDTH__", max_bits.to_string());
push("__SIZEOF_INTMAX__", (max_bits / 8).to_string());
push("__UINTMAX_TYPE__", max_unsigned.to_owned());
push(
"__UINTMAX_MAX__",
format!("{}U{max_suffix}", unsigned_max(max_bits)),
);
push("__UINTMAX_WIDTH__", max_bits.to_string());
push("__INTPTR_TYPE__", ptr_signed.to_owned());
push(
"__INTPTR_MAX__",
format!("{}{ptr_suffix}", signed_max(ptr_bits)),
);
push("__INTPTR_WIDTH__", ptr_bits.to_string());
push("__UINTPTR_TYPE__", ptr_unsigned.to_owned());
push(
"__UINTPTR_MAX__",
format!("{}U{ptr_suffix}", unsigned_max(ptr_bits)),
);
push("__UINTPTR_WIDTH__", ptr_bits.to_string());
push("__POINTER_WIDTH__", ptr_bits.to_string());
let wchar_bits = target.wchar_bits;
let (wchar_type, wchar_max, wchar_min) = if target.wchar_signed {
(
if wchar_bits == 16 { "short int" } else { "int" },
signed_max(wchar_bits),
format!("(-{}-1)", signed_max(wchar_bits)),
)
} else {
(
if wchar_bits == 16 {
"short unsigned int"
} else {
"unsigned int"
},
unsigned_max(wchar_bits),
"0".to_owned(),
)
};
push("__WCHAR_TYPE__", wchar_type.to_owned());
push("__WCHAR_MAX__", wchar_max);
push("__WCHAR_MIN__", wchar_min);
push("__WCHAR_WIDTH__", wchar_bits.to_string());
push("__SIZEOF_WCHAR_T__", (wchar_bits / 8).to_string());
if !target.wchar_signed {
push("__WCHAR_UNSIGNED__", "1".to_owned());
}
let wint_bits = target.wint_bits;
push(
"__WINT_TYPE__",
match (target.wint_signed, wint_bits) {
(true, 16) => "short int",
(true, _) => "int",
(false, 16) => "short unsigned int",
(false, _) => "unsigned int",
}
.to_owned(),
);
push("__WINT_WIDTH__", wint_bits.to_string());
push("__SIZEOF_WINT_T__", (wint_bits / 8).to_string());
push("__SIG_ATOMIC_TYPE__", "int".to_owned());
push("__SIG_ATOMIC_MAX__", signed_max(int_bits));
push(
"__SIG_ATOMIC_MIN__",
format!("(-{}-1)", signed_max(int_bits)),
);
push("__SIG_ATOMIC_WIDTH__", int_bits.to_string());
push("__CHAR16_TYPE__", "short unsigned int".to_owned());
push("__CHAR32_TYPE__", "unsigned int".to_owned());
push("__SIZEOF_FLOAT__", "4".to_owned());
push("__SIZEOF_DOUBLE__", "8".to_owned());
push("__SIZEOF_LONG_DOUBLE__", "8".to_owned());
push("__FLT_RADIX__", "2".to_owned());
push("__FLT_EVAL_METHOD__", "0".to_owned());
push("__FLT_MANT_DIG__", "24".to_owned());
push("__FLT_DIG__", "6".to_owned());
push("__FLT_MIN_EXP__", "(-125)".to_owned());
push("__FLT_MIN_10_EXP__", "(-37)".to_owned());
push("__FLT_MAX_EXP__", "128".to_owned());
push("__FLT_MAX_10_EXP__", "38".to_owned());
push("__FLT_DECIMAL_DIG__", "9".to_owned());
push("__FLT_MAX__", "3.40282346638528859812e+38F".to_owned());
push("__FLT_NORM_MAX__", "3.40282346638528859812e+38F".to_owned());
push("__FLT_MIN__", "1.17549435082228750797e-38F".to_owned());
push("__FLT_EPSILON__", "1.19209289550781250000e-7F".to_owned());
push(
"__FLT_DENORM_MIN__",
"1.40129846432481707092e-45F".to_owned(),
);
push("__FLT_HAS_DENORM__", "1".to_owned());
push("__FLT_HAS_INFINITY__", "1".to_owned());
push("__FLT_HAS_QUIET_NAN__", "1".to_owned());
push("__FLT_IS_IEC_60559__", "1".to_owned());
push("__DBL_MANT_DIG__", "53".to_owned());
push("__DBL_DIG__", "15".to_owned());
push("__DBL_MIN_EXP__", "(-1021)".to_owned());
push("__DBL_MIN_10_EXP__", "(-307)".to_owned());
push("__DBL_MAX_EXP__", "1024".to_owned());
push("__DBL_MAX_10_EXP__", "308".to_owned());
push("__DBL_DECIMAL_DIG__", "17".to_owned());
push("__DBL_MAX__", "1.79769313486231570815e+308".to_owned());
push("__DBL_NORM_MAX__", "1.79769313486231570815e+308".to_owned());
push("__DBL_MIN__", "2.22507385850720138309e-308".to_owned());
push("__DBL_EPSILON__", "2.22044604925031308085e-16".to_owned());
push(
"__DBL_DENORM_MIN__",
"4.94065645841246544177e-324".to_owned(),
);
push("__DBL_HAS_DENORM__", "1".to_owned());
push("__DBL_HAS_INFINITY__", "1".to_owned());
push("__DBL_HAS_QUIET_NAN__", "1".to_owned());
push("__DBL_IS_IEC_60559__", "1".to_owned());
push("__LDBL_MANT_DIG__", "53".to_owned());
push("__LDBL_DIG__", "15".to_owned());
push("__LDBL_MIN_EXP__", "(-1021)".to_owned());
push("__LDBL_MIN_10_EXP__", "(-307)".to_owned());
push("__LDBL_MAX_EXP__", "1024".to_owned());
push("__LDBL_MAX_10_EXP__", "308".to_owned());
push("__LDBL_DECIMAL_DIG__", "17".to_owned());
push("__DECIMAL_DIG__", "17".to_owned());
push("__LDBL_MAX__", "1.79769313486231570815e+308L".to_owned());
push(
"__LDBL_NORM_MAX__",
"1.79769313486231570815e+308L".to_owned(),
);
push("__LDBL_MIN__", "2.22507385850720138309e-308L".to_owned());
push("__LDBL_EPSILON__", "2.22044604925031308085e-16L".to_owned());
push(
"__LDBL_DENORM_MIN__",
"4.94065645841246544177e-324L".to_owned(),
);
push("__LDBL_HAS_DENORM__", "1".to_owned());
push("__LDBL_HAS_INFINITY__", "1".to_owned());
push("__LDBL_HAS_QUIET_NAN__", "1".to_owned());
push("__LDBL_IS_IEC_60559__", "1".to_owned());
let (i64_type, u64_type, s64, u64) = if long_bits == 64 {
("long int", "long unsigned int", "L", "UL")
} else {
("long long int", "long long unsigned int", "LL", "ULL")
};
let widths: [(
&'static str,
&'static str,
&'static str,
&'static str,
&'static str,
u32,
); 4] = [
("8", "signed char", "unsigned char", "", "", 8),
("16", "short int", "short unsigned int", "", "", 16),
("32", "int", "unsigned int", "", "U", 32),
("64", i64_type, u64_type, s64, u64, 64),
];
const EXACT: [[&str; 8]; 4] = [
[
"__INT8_TYPE__",
"__UINT8_TYPE__",
"__INT8_MAX__",
"__UINT8_MAX__",
"__INT_LEAST8_TYPE__",
"__UINT_LEAST8_TYPE__",
"__INT_LEAST8_MAX__",
"__UINT_LEAST8_MAX__",
],
[
"__INT16_TYPE__",
"__UINT16_TYPE__",
"__INT16_MAX__",
"__UINT16_MAX__",
"__INT_LEAST16_TYPE__",
"__UINT_LEAST16_TYPE__",
"__INT_LEAST16_MAX__",
"__UINT_LEAST16_MAX__",
],
[
"__INT32_TYPE__",
"__UINT32_TYPE__",
"__INT32_MAX__",
"__UINT32_MAX__",
"__INT_LEAST32_TYPE__",
"__UINT_LEAST32_TYPE__",
"__INT_LEAST32_MAX__",
"__UINT_LEAST32_MAX__",
],
[
"__INT64_TYPE__",
"__UINT64_TYPE__",
"__INT64_MAX__",
"__UINT64_MAX__",
"__INT_LEAST64_TYPE__",
"__UINT_LEAST64_TYPE__",
"__INT_LEAST64_MAX__",
"__UINT_LEAST64_MAX__",
],
];
for (names, (_, signed, unsigned, s_suffix, u_suffix, bits)) in EXACT.iter().zip(widths) {
let smax = format!("{}{s_suffix}", signed_max(bits));
let umax = format!("{}{u_suffix}", unsigned_max(bits));
for at in [0, 4] {
out.push((names[at], signed.to_owned()));
out.push((names[at + 1], unsigned.to_owned()));
out.push((names[at + 2], smax.clone()));
out.push((names[at + 3], umax.clone()));
}
}
let fast_mid = if ptr_bits == 64 { "64" } else { "32" };
for (name, value) in [
("__INT_LEAST8_WIDTH__", "8"),
("__INT_LEAST16_WIDTH__", "16"),
("__INT_LEAST32_WIDTH__", "32"),
("__INT_LEAST64_WIDTH__", "64"),
("__INT_FAST8_WIDTH__", "8"),
("__INT_FAST16_WIDTH__", fast_mid),
("__INT_FAST32_WIDTH__", fast_mid),
("__INT_FAST64_WIDTH__", "64"),
] {
out.push((name, value.to_owned()));
}
}