use rucc_base::{Interner, Symbol};
use rucc_diag::{BytePos, Diagnostic, SourceMap, Span};
use rucc_lex::{Options, PpToken, PpTokenKind, Punct, TokenFlags, tokenize};
use crate::hide::{HideSet, HideSets};
use crate::include::{UNKNOWN, base_name, quoted};
use crate::macros::{Builtin, MacroDef, MacroTable};
use crate::token::Tok;
use crate::trace::{TraceId, Traces};
const MAX_STEPS: usize = 1 << 24;
#[derive(Debug, Default)]
pub struct Expander {
hides: HideSets,
traces: Traces,
diagnostics: Vec<Diagnostic>,
counter: u32,
}
impl Expander {
pub fn new() -> Expander {
Expander {
hides: HideSets::new(),
traces: Traces::new(),
diagnostics: Vec::new(),
counter: 0,
}
}
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
std::mem::take(&mut self.diagnostics)
}
pub fn hide_sets(&self) -> usize {
self.hides.len()
}
pub fn expand(
&mut self,
tokens: &[PpToken],
macros: &MacroTable,
interner: &mut Interner,
sources: &SourceMap,
) -> Vec<Tok> {
let input: Vec<Tok> =
tokens.iter().filter(|t| t.kind != PpTokenKind::Eof).map(|&t| Tok::new(t)).collect();
self.expand_toks(input, macros, interner, sources)
}
pub fn expand_toks(
&mut self,
tokens: Vec<Tok>,
macros: &MacroTable,
interner: &mut Interner,
sources: &SourceMap,
) -> Vec<Tok> {
let mut run = Run {
hides: &mut self.hides,
traces: &mut self.traces,
current: TraceId::NONE,
diagnostics: &mut self.diagnostics,
macros,
va_opt: interner.intern("__VA_OPT__"),
interner,
sources,
counter: &mut self.counter,
steps: 0,
};
run.expand(tokens)
}
}
struct Run<'a> {
hides: &'a mut HideSets,
traces: &'a mut Traces,
current: TraceId,
diagnostics: &'a mut Vec<Diagnostic>,
interner: &'a mut Interner,
macros: &'a MacroTable,
sources: &'a SourceMap,
va_opt: Symbol,
counter: &'a mut u32,
steps: usize,
}
impl<'a> Run<'a> {
fn expand(&mut self, input: Vec<Tok>) -> Vec<Tok> {
let macros = self.macros;
let mut pending = input;
pending.reverse();
let mut out: Vec<Tok> = Vec::with_capacity(pending.len());
while let Some(tok) = pending.pop() {
self.steps += 1;
if self.steps > MAX_STEPS {
let d = Diagnostic::error("macro expansion is too large", tok.report_span())
.with_code("E0310")
.note("expansion stopped here, the rest of the line is not expanded", tok.span);
let d = self.in_expansions(d, tok.trace, tok.span);
self.diagnostics.push(d);
out.push(tok);
pending.reverse();
out.append(&mut pending);
return out;
}
let Some(name) = tok.ident() else {
out.push(tok);
continue;
};
if self.hides.contains(tok.hides, name) {
out.push(tok);
continue;
}
let Some(def) = macros.lookup(name) else {
out.push(tok);
continue;
};
if let Some(builtin) = def.builtin {
let value = self.builtin_value(builtin, tok);
out.push(value);
continue;
}
if !def.function_like {
let hs = self.hides.add(tok.hides, name);
let mut args = Args::none();
let replacement = self.subst(def, &mut args, hs, tok);
push_front(&mut pending, replacement, tok);
continue;
}
if !pending.last().is_some_and(|t| t.is(Punct::LParen)) {
out.push(tok);
continue;
}
let Some((raw, rparen)) = self.collect_args(def, &mut pending, tok) else {
out.push(tok);
continue;
};
let shared = self.hides.intersect(tok.hides, rparen.hides);
let hs = self.hides.add(shared, name);
let mut args = Args::new(raw, tok.trace);
let replacement = self.subst(def, &mut args, hs, tok);
push_front(&mut pending, replacement, tok);
}
out
}
fn builtin_value(&mut self, which: Builtin, tok: Tok) -> Tok {
let at = tok.report_span().lo;
let (kind, text) = match which {
Builtin::File => (PpTokenKind::StringLit, quoted(self.name_of(at))),
Builtin::FileName => (PpTokenKind::StringLit, quoted(base_name(self.name_of(at)))),
Builtin::BaseFile => (PpTokenKind::StringLit, quoted(self.base_file(at))),
Builtin::Line => (PpTokenKind::Number, self.line_of(at).to_string()),
Builtin::IncludeLevel => {
(PpTokenKind::Number, self.sources.include_stack(at).len().to_string())
}
Builtin::Counter => {
let value = *self.counter;
*self.counter = self.counter.saturating_add(1);
(PpTokenKind::Number, value.to_string())
}
};
Tok {
kind,
flags: tok.flags,
value: Some(self.interner.intern(&text)),
span: tok.span,
expansion: tok.expansion,
trace: tok.trace,
hides: tok.hides,
placemarker: false,
}
}
fn name_of(&self, at: BytePos) -> &str {
match self.sources.lookup_file(at) {
Some(file) => &self.sources.file(file).name,
None => UNKNOWN,
}
}
fn line_of(&self, at: BytePos) -> u32 {
self.sources.lookup(at).map_or(0, |loc| loc.line)
}
fn base_file(&self, at: BytePos) -> &str {
match self.sources.include_stack(at).last() {
Some(outermost) => self.name_of(outermost.lo),
None => self.name_of(at),
}
}
fn collect_args(
&mut self,
def: &MacroDef,
pending: &mut Vec<Tok>,
name: Tok,
) -> Option<(Vec<Vec<Tok>>, Tok)> {
let open = pending.pop().expect("the caller checked for an opening parenthesis");
let mut args: Vec<Vec<Tok>> = Vec::with_capacity(def.arity() + 1);
let mut current: Vec<Tok> = Vec::new();
let mut depth = 1usize;
let rparen = loop {
let Some(tok) = pending.pop() else {
let d = Diagnostic::error("unterminated macro argument list", open.report_span())
.with_code("E0311")
.note("this macro was invoked here", name.report_span());
let d = self.in_expansions(d, name.trace, name.span);
self.diagnostics.push(d);
return None;
};
match tok.punct() {
Some(Punct::LParen) => {
depth += 1;
current.push(tok);
}
Some(Punct::RParen) => {
depth -= 1;
if depth == 0 {
break tok;
}
current.push(tok);
}
Some(Punct::Comma)
if depth == 1 && !(def.is_variadic() && args.len() >= def.arity()) =>
{
args.push(std::mem::take(&mut current));
}
_ => current.push(tok),
}
};
let empty_invocation = args.is_empty() && current.is_empty();
if !(empty_invocation && def.arity() == 0 && !def.is_variadic()) {
args.push(current);
}
if def.is_variadic() && args.len() == def.arity() {
args.push(Vec::new());
}
let expected = def.arity() + usize::from(def.is_variadic());
if args.len() != expected {
let word = if args.len() < expected { "few" } else { "many" };
let d = Diagnostic::error(
format!(
"too {word} arguments to macro `{}`, expected {}{}, got {}",
self.interner.resolve(def.name),
def.arity(),
if def.is_variadic() { " or more" } else { "" },
args.len()
),
name.report_span(),
)
.with_code("E0312")
.note("defined here", def.span);
let d = self.in_expansions(d, name.trace, name.span);
self.diagnostics.push(d);
return None;
}
Some((args, rparen))
}
fn in_expansions(&self, mut d: Diagnostic, trace: TraceId, innermost: Span) -> Diagnostic {
let chain = self.traces.chain(trace);
for (i, step) in chain.iter().enumerate() {
let at = chain.get(i + 1).map_or(innermost, |next| next.at);
let name = self.interner.resolve(step.macro_name);
d = d.note(format!("expanded from macro `{name}`"), at);
}
d
}
fn subst(&mut self, def: &MacroDef, args: &mut Args, hs: HideSet, invocation: Tok) -> Vec<Tok> {
let name = invocation.ident();
let here = match name {
Some(name) => self.traces.push(name, invocation.span, invocation.trace),
None => invocation.trace,
};
let outer = std::mem::replace(&mut self.current, here);
let body: Vec<Tok> =
def.body.iter().map(|&t| Tok { trace: invocation.trace, ..Tok::new(t) }).collect();
let substituted = self.subst_list(def, args, &body, invocation);
self.current = outer;
let mut os = drop_placemarkers(substituted);
for tok in &mut os {
tok.hides = self.hides.union(tok.hides, hs);
tok.expansion = invocation.report_span();
if let Some(name) = name {
tok.trace = self.traces.push(name, invocation.span, tok.trace);
}
}
if let Some(first) = os.first_mut() {
first.flags = carried_spacing(invocation.flags);
}
os
}
fn subst_list(
&mut self,
def: &MacroDef,
args: &mut Args,
is: &[Tok],
invocation: Tok,
) -> Vec<Tok> {
let mut os: Vec<Tok> = Vec::with_capacity(is.len());
let mut at = 0;
let mut owed = false;
while at < is.len() {
let tok = is[at];
if def.function_like && tok.is(Punct::Hash) {
if let Some(next) = is.get(at + 1) {
if let Some(idx) = next.ident().and_then(|s| def.param_index(s)) {
let text = self.stringize(args.raw(idx));
let string = self.string_token(&text, tok.span.to(next.span));
emit(&mut os, &[string], tok, &mut owed);
at += 2;
continue;
}
if next.ident() == Some(self.va_opt) {
if let Some(inner) = va_opt_group(is, at + 1) {
let close = inner.end;
let raw = if args.raw(def.arity()).is_empty() {
Vec::new()
} else {
self.subst_raw(def, args, &is[inner])
};
let text = self.stringize(&raw);
let string = self.string_token(&text, tok.span.to(is[close].span));
emit(&mut os, &[string], tok, &mut owed);
at = close + 1;
continue;
}
}
}
}
if tok.is(Punct::HashHash) {
let next = is[at + 1];
owed = false;
let param = next.ident().and_then(|s| def.param_index(s).map(|idx| (s, idx)));
if let Some((name, idx)) = param {
let raw = args.raw(idx).to_vec();
let comma_variadic = def.is_variadic_param(name)
&& os.last().is_some_and(|l| l.is(Punct::Comma));
if comma_variadic {
if raw.is_empty() {
os.pop();
} else {
emit(&mut os, &raw, next, &mut owed);
}
} else {
self.glue(&mut os, &raw, next.span, tok.span);
}
at += 2;
continue;
}
if next.ident() == Some(self.va_opt) {
if let Some(inner) = va_opt_group(is, at + 1) {
let close = inner.end;
let rhs = self.va_opt_value(def, args, &is[inner], invocation, next.span);
self.glue(&mut os, &rhs, next.span, tok.span);
at = close + 1;
continue;
}
}
self.glue(&mut os, &[next], next.span, tok.span);
at += 2;
continue;
}
if tok.ident() == Some(self.va_opt) {
if let Some(inner) = va_opt_group(is, at) {
let close = inner.end;
let value = self.va_opt_value(def, args, &is[inner], invocation, tok.span);
emit(&mut os, &value, tok, &mut owed);
at = close + 1;
continue;
}
}
if let Some(idx) = tok.ident().and_then(|s| def.param_index(s)) {
if is.get(at + 1).is_some_and(|n| n.is(Punct::HashHash)) {
let raw = args.raw(idx).to_vec();
let placemarker = [Tok::placemarker_at(tok.span)];
let value = if raw.is_empty() { &placemarker[..] } else { &raw[..] };
emit(&mut os, value, tok, &mut owed);
} else {
let expanded = args.expanded(idx, self).to_vec();
emit(&mut os, &expanded, tok, &mut owed);
}
at += 1;
continue;
}
emit_plain(&mut os, tok, &mut owed);
at += 1;
}
os
}
fn va_opt_value(
&mut self,
def: &MacroDef,
args: &mut Args,
inner: &[Tok],
invocation: Tok,
span: Span,
) -> Vec<Tok> {
if args.raw(def.arity()).is_empty() {
return vec![Tok::placemarker_at(span)];
}
let value = self.subst_list(def, args, inner, invocation);
if value.is_empty() { vec![Tok::placemarker_at(span)] } else { value }
}
fn subst_raw(&mut self, def: &MacroDef, args: &mut Args, inner: &[Tok]) -> Vec<Tok> {
let mut out = Vec::with_capacity(inner.len());
for &tok in inner {
match tok.ident().and_then(|s| def.param_index(s)) {
Some(idx) => out.extend_from_slice(args.raw(idx)),
None => out.push(tok),
}
}
out
}
fn glue(&mut self, os: &mut Vec<Tok>, rhs: &[Tok], span: Span, op: Span) {
let placemarker = [Tok::placemarker_at(span)];
let rhs = if rhs.is_empty() { &placemarker[..] } else { rhs };
let Some(lhs) = os.pop() else {
os.extend_from_slice(rhs);
return;
};
let first = rhs[0];
if lhs.is_placemarker() {
os.extend_from_slice(rhs);
return;
}
if first.is_placemarker() {
os.push(lhs);
os.extend_from_slice(&rhs[1..]);
return;
}
match self.paste(lhs, first, op) {
Some(joined) => os.push(joined),
None => {
let mut first = first;
first.flags = TokenFlags::EMPTY;
os.push(lhs);
os.push(first);
}
}
os.extend_from_slice(&rhs[1..]);
}
fn paste(&mut self, lhs: Tok, rhs: Tok, op: Span) -> Option<Tok> {
let mut text = String::new();
self.spell(lhs, &mut text);
let split = text.len();
self.spell(rhs, &mut text);
let (tokens, _) = tokenize(text.as_bytes(), 0, Options::new(), self.interner);
let single = tokens.len() == 2
&& tokens[0].kind != PpTokenKind::Eof
&& tokens[1].kind == PpTokenKind::Eof
&& tokens[0].span.lo == 0
&& tokens[0].span.hi as usize == text.len();
if !single {
let d = Diagnostic::error(
format!(
"pasting `{}` and `{}` does not give a valid preprocessing token",
&text[..split],
&text[split..]
),
lhs.report_span().to(rhs.report_span()),
)
.with_code("E0313")
.note("the left operand is here", lhs.span)
.note("the right operand is here", rhs.span);
let d = self.in_expansions(d, self.current, op);
self.diagnostics.push(d);
return None;
}
Some(Tok {
kind: tokens[0].kind,
flags: lhs.flags,
value: tokens[0].value,
span: lhs.span.to(rhs.span),
expansion: lhs.expansion,
trace: lhs.trace,
hides: self.hides.union(lhs.hides, rhs.hides),
placemarker: false,
})
}
fn stringize(&self, toks: &[Tok]) -> String {
let mut out = String::from("\"");
let mut first = true;
for &tok in toks.iter().filter(|t| !t.is_placemarker()) {
if !first && tok.flags.has(TokenFlags::LEADING_SPACE) {
out.push(' ');
}
first = false;
let mut spelled = String::new();
self.spell(tok, &mut spelled);
if matches!(tok.kind, PpTokenKind::StringLit | PpTokenKind::CharConst) {
for ch in spelled.chars() {
if ch == '\\' || ch == '"' {
out.push('\\');
}
out.push(ch);
}
} else {
out.push_str(&spelled);
}
}
out.push('"');
out
}
fn string_token(&mut self, text: &str, span: Span) -> Tok {
Tok {
kind: PpTokenKind::StringLit,
flags: TokenFlags::EMPTY,
value: Some(self.interner.intern(text)),
span,
expansion: Span::DUMMY,
trace: TraceId::NONE,
hides: HideSet::EMPTY,
placemarker: false,
}
}
fn spell(&self, tok: Tok, out: &mut String) {
if tok.is_placemarker() {
return;
}
match (tok.kind, tok.value) {
(PpTokenKind::Punct(p), _) => out.push_str(p.as_str()),
(_, Some(sym)) => out.push_str(self.interner.resolve(sym)),
(_, None) => {}
}
}
}
fn emit(os: &mut Vec<Tok>, value: &[Tok], source: Tok, owed: &mut bool) {
let Some((&first, rest)) = value.split_first() else {
*owed = *owed || source.flags.has(TokenFlags::LEADING_SPACE);
return;
};
let mut first = first;
first.flags = carried_spacing(source.flags);
if *owed {
first.flags = first.flags.with(TokenFlags::LEADING_SPACE);
*owed = false;
}
os.push(first);
os.extend_from_slice(rest);
}
fn emit_plain(os: &mut Vec<Tok>, tok: Tok, owed: &mut bool) {
let mut tok = tok;
if *owed {
tok.flags = tok.flags.with(TokenFlags::LEADING_SPACE);
*owed = false;
}
os.push(tok);
}
fn drop_placemarkers(toks: Vec<Tok>) -> Vec<Tok> {
let mut out = Vec::with_capacity(toks.len());
let mut owed = false;
for tok in toks {
if tok.is_placemarker() {
owed = owed || tok.flags.has(TokenFlags::LEADING_SPACE);
continue;
}
emit_plain(&mut out, tok, &mut owed);
}
out
}
fn va_opt_group(is: &[Tok], at: usize) -> Option<std::ops::Range<usize>> {
if !is.get(at + 1).is_some_and(|t| t.is(Punct::LParen)) {
return None;
}
let start = at + 2;
let mut depth = 1usize;
let mut end = start;
while end < is.len() {
match is[end].punct() {
Some(Punct::LParen) => depth += 1,
Some(Punct::RParen) => {
depth -= 1;
if depth == 0 {
return Some(start..end);
}
}
_ => {}
}
end += 1;
}
None
}
fn push_front(pending: &mut Vec<Tok>, mut replacement: Vec<Tok>, invocation: Tok) {
if replacement.is_empty() {
if invocation.flags.has(TokenFlags::LEADING_SPACE) {
if let Some(next) = pending.last_mut() {
next.flags = next.flags.with(TokenFlags::LEADING_SPACE);
}
}
return;
}
replacement.reverse();
pending.append(&mut replacement);
}
fn carried_spacing(flags: TokenFlags) -> TokenFlags {
let mut carried = TokenFlags::EMPTY;
if flags.has(TokenFlags::START_OF_LINE) {
carried = carried.with(TokenFlags::START_OF_LINE);
}
if flags.has(TokenFlags::LEADING_SPACE) {
carried = carried.with(TokenFlags::LEADING_SPACE);
}
carried
}
struct Args {
raw: Vec<Vec<Tok>>,
expanded: Vec<Option<Vec<Tok>>>,
outer: TraceId,
}
impl Args {
fn new(raw: Vec<Vec<Tok>>, outer: TraceId) -> Args {
let count = raw.len();
Args { raw, expanded: vec![None; count], outer }
}
fn none() -> Args {
Args { raw: Vec::new(), expanded: Vec::new(), outer: TraceId::NONE }
}
fn raw(&self, idx: usize) -> &[Tok] {
self.raw.get(idx).map_or(&[][..], |a| a.as_slice())
}
fn expanded(&mut self, idx: usize, run: &mut Run<'_>) -> &[Tok] {
let Some(slot) = self.expanded.get(idx) else {
return &[];
};
if slot.is_none() {
let saved = std::mem::replace(&mut run.current, self.outer);
let expanded = run.expand(self.raw[idx].clone());
run.current = saved;
self.expanded[idx] = Some(expanded);
}
self.expanded[idx].as_deref().expect("just filled in")
}
}