use rucc_base::Symbol;
use rucc_diag::Span;
use rucc_lex::{Token, TokenKind};
use crate::parser::Parser;
#[derive(Debug, Default)]
pub(crate) struct Packs {
next: usize,
current: Option<u32>,
stack: Vec<Option<u32>>,
}
const ALLOWED: [u32; 6] = [0, 1, 2, 4, 8, 16];
#[derive(Debug, Clone, Copy)]
enum Action {
Set(u32),
Push(Option<u32>),
Pop(Option<Symbol>),
}
impl Parser<'_> {
pub(crate) fn pack_in_effect(&mut self) -> Option<u32> {
self.read_packs(self.cursor.index());
self.packs.current
}
pub(crate) fn finish_packs(&mut self) {
self.read_packs(usize::MAX);
}
fn read_packs(&mut self, to: usize) {
while let Some(pragma) = self.tokens.pragmas.get(self.packs.next) {
if pragma.before as usize >= to {
return;
}
self.packs.next += 1;
let (line, span) = (pragma.tokens.clone(), pragma.span);
self.pack_line(&line, span);
}
}
fn pack_line(&mut self, line: &[Token], span: Span) {
let Some(first) = line.first() else { return };
if first.ident().is_none_or(|name| self.cx.interner.resolve(name) != "pack") {
return;
}
let mut rest = &line[1..];
if !eat_punct(&mut rest, "(") {
self.warn("E0677", "missing `(` after `#pragma pack` - ignored", span);
return;
}
let Some(action) = self.pack_action(&mut rest, span) else { return };
if !eat_punct(&mut rest, ")") {
let form = match action {
Action::Set(_) => "`#pragma pack`",
Action::Push(_) => "`#pragma pack(push[, id][, <n>])`",
Action::Pop(_) => "`#pragma pack(pop[, id])`",
};
self.warn("E0677", format!("malformed {form} - ignored"), span);
return;
}
self.apply_pack(action, span);
self.junk(rest, span);
}
fn pack_action(&mut self, rest: &mut &[Token], span: Span) -> Option<Action> {
if rest.is_empty() {
self.warn("E0677", "malformed `#pragma pack` - ignored", span);
return None;
}
if rest.first().is_some_and(is_rparen) {
return Some(Action::Set(0));
}
let Some(word) = rest.first().and_then(|token| token.ident()) else {
return Some(Action::Set(self.pack_number(rest, span)?));
};
match self.cx.interner.resolve(word) {
"push" => {
*rest = &rest[1..];
self.pack_push(rest, span)
}
"pop" => {
*rest = &rest[1..];
let named = if eat_punct(rest, ",") { self.pack_name(rest) } else { None };
Some(Action::Pop(named))
}
other => {
let what = format!("unknown action `{other}` for `#pragma pack` - ignored");
self.warn("E0681", what, span);
None
}
}
}
fn pack_push(&mut self, rest: &mut &[Token], span: Span) -> Option<Action> {
if !eat_punct(rest, ",") {
return Some(Action::Push(None));
}
if self.pack_name(rest).is_some() && !eat_punct(rest, ",") {
return Some(Action::Push(None));
}
Some(Action::Push(Some(self.pack_number(rest, span)?)))
}
fn apply_pack(&mut self, action: Action, span: Span) {
match action {
Action::Set(bytes) => self.packs.current = in_effect(bytes),
Action::Push(bytes) => {
self.packs.stack.push(self.packs.current);
if let Some(bytes) = bytes {
self.packs.current = in_effect(bytes);
}
}
Action::Pop(named) => match self.packs.stack.pop() {
Some(saved) => self.packs.current = saved,
None => {
let what = match named {
Some(name) => {
let name = self.cx.interner.resolve(name);
format!(
"`#pragma pack(pop, {name})` encountered without matching \
`#pragma pack(push, {name})`"
)
}
None => "`#pragma pack (pop)` encountered without matching \
`#pragma pack (push)`"
.to_string(),
};
self.warn("E0678", what, span);
}
},
}
}
fn pack_number(&mut self, rest: &mut &[Token], span: Span) -> Option<u32> {
let Some(value) =
rest.first().and_then(|token| self.tokens.int(*token)).map(|int| int.value)
else {
self.warn("E0677", "malformed `#pragma pack` - ignored", span);
return None;
};
*rest = &rest[1..];
let allowed = u32::try_from(value).is_ok_and(|value| ALLOWED.contains(&value));
if !allowed {
let what = format!("alignment must be a small power of two, not {value}");
self.warn("E0679", what, span);
return None;
}
u32::try_from(value).ok()
}
fn pack_name(&mut self, rest: &mut &[Token]) -> Option<Symbol> {
let name = rest.first().and_then(|token| token.ident())?;
*rest = &rest[1..];
Some(name)
}
fn junk(&mut self, rest: &[Token], span: Span) {
if !rest.is_empty() {
self.warn("E0680", "junk at end of `#pragma pack`", span);
}
}
}
fn in_effect(bytes: u32) -> Option<u32> {
(bytes != 0).then_some(bytes)
}
fn is_rparen(token: &Token) -> bool {
matches!(token.kind, TokenKind::Punct(punct) if punct.as_str() == ")")
}
fn eat_punct(rest: &mut &[Token], spelling: &str) -> bool {
let found = match rest.first().map(|token| token.kind) {
Some(TokenKind::Punct(punct)) => punct.as_str() == spelling,
_ => false,
};
if found {
*rest = &rest[1..];
}
found
}