use rucc_base::{Interner, Symbol};
use rucc_diag::{Diagnostic, Span};
use rucc_lex::{PpTokenKind, Punct, TokenFlags};
use crate::cond;
use crate::include::spelling;
use crate::token::Tok;
#[derive(Debug, Default)]
pub(crate) struct Params {
pub(crate) limit: Option<u64>,
pub(crate) offset: u64,
pub(crate) prefix: Vec<Tok>,
pub(crate) suffix: Vec<Tok>,
pub(crate) if_empty: Vec<Tok>,
}
impl Params {
pub(crate) fn taken(&self, len: u64) -> u64 {
let after_offset = len.saturating_sub(self.offset);
match self.limit {
Some(limit) => after_offset.min(limit),
None => after_offset,
}
}
}
struct Names {
limit: Symbol,
prefix: Symbol,
suffix: Symbol,
if_empty: Symbol,
gnu: Symbol,
offset: Symbol,
}
impl Names {
fn new(interner: &mut Interner) -> Names {
Names {
limit: interner.intern("limit"),
prefix: interner.intern("prefix"),
suffix: interner.intern("suffix"),
if_empty: interner.intern("if_empty"),
gnu: interner.intern("gnu"),
offset: interner.intern("offset"),
}
}
}
pub(crate) fn header_length(line: &[Tok]) -> Option<usize> {
match line.first()?.kind {
PpTokenKind::HeaderName | PpTokenKind::StringLit => Some(1),
PpTokenKind::Punct(Punct::Lt) => {
let end = line.iter().position(|t| t.is(Punct::Gt))?;
Some(end + 1)
}
_ => None,
}
}
pub(crate) fn parse(
line: &[Tok],
at: Span,
interner: &mut Interner,
diagnostics: &mut Vec<Diagnostic>,
expand: &mut dyn FnMut(Vec<Tok>, &mut Interner) -> Vec<Tok>,
) -> Option<Params> {
let names = Names::new(interner);
let mut params = Params::default();
let mut seen: Vec<Symbol> = Vec::new();
let mut rest = line;
while let Some(first) = rest.first().copied() {
let Some(name) = first.ident() else {
diagnostics.push(
Diagnostic::error(
format!(
"expected an `#embed` parameter, found `{}`",
spelling(first, interner)
),
first.report_span(),
)
.with_code("E0346"),
);
return None;
};
let (scope, name, used) = match (rest.get(1), rest.get(2).and_then(|t| t.ident())) {
(Some(colons), Some(inner)) if colons.is(Punct::ColonColon) => {
(Some(name), inner, 3usize)
}
_ => (None, name, 1usize),
};
rest = &rest[used..];
let (operand, after) = match arguments(rest) {
Some((operand, after)) => (operand, after),
None => (&rest[..0], 0),
};
rest = &rest[after..];
if seen.contains(&name) {
diagnostics.push(
Diagnostic::error("`#embed` parameter given twice", first.report_span())
.with_code("E0347"),
);
return None;
}
seen.push(name);
let where_written = first.report_span();
match (scope, name) {
(None, n) if n == names.limit => {
let limit = count(operand, "limit", where_written, interner, diagnostics, expand)?;
params.limit = Some(limit);
}
(Some(s), n) if s == names.gnu && n == names.offset => {
params.offset =
count(operand, "offset", where_written, interner, diagnostics, expand)?;
}
(None, n) if n == names.prefix => params.prefix = expand(operand.to_vec(), interner),
(None, n) if n == names.suffix => params.suffix = expand(operand.to_vec(), interner),
(None, n) if n == names.if_empty => {
params.if_empty = expand(operand.to_vec(), interner);
}
_ => {
let written = match scope {
Some(s) => format!("{}::{}", interner.resolve(s), interner.resolve(name)),
None => interner.resolve(name).to_owned(),
};
diagnostics.push(
Diagnostic::error(format!("unknown `#embed` parameter `{written}`"), at)
.with_code("E0349")
.note("known parameters are `limit`, `prefix`, `suffix`, `if_empty` and `gnu::offset`", at),
);
return None;
}
}
}
Some(params)
}
fn count(
operand: &[Tok],
what: &str,
at: Span,
interner: &mut Interner,
diagnostics: &mut Vec<Diagnostic>,
expand: &mut dyn FnMut(Vec<Tok>, &mut Interner) -> Vec<Tok>,
) -> Option<u64> {
let expanded = expand(operand.to_vec(), interner);
let value = cond::value(&expanded, interner, diagnostics, at, what)?;
if value < 0 {
diagnostics.push(
Diagnostic::error(format!("`{what}` must not be negative"), at).with_code("E0348"),
);
return None;
}
Some(value as u64)
}
fn arguments(line: &[Tok]) -> Option<(&[Tok], usize)> {
if !line.first()?.is(Punct::LParen) {
return None;
}
let mut depth = 1u32;
for (at, tok) in line.iter().enumerate().skip(1) {
if tok.is(Punct::LParen) {
depth += 1;
} else if tok.is(Punct::RParen) {
depth -= 1;
if depth == 0 {
return Some((&line[1..at], at + 1));
}
}
}
None
}
pub(crate) fn tokens(
bytes: &[u8],
params: &Params,
hash: Span,
interner: &mut Interner,
out: &mut Vec<Tok>,
) {
let began = out.len();
let taken = params.taken(bytes.len() as u64);
if taken == 0 {
out.extend(params.if_empty.iter().copied());
opens_the_line(out, began);
return;
}
let start = params.offset as usize;
let bytes = &bytes[start..start + taken as usize];
out.extend(params.prefix.iter().copied());
let mut spellings: [Option<Symbol>; 256] = [None; 256];
let mut first = true;
for &byte in bytes {
if !first {
out.push(Tok::synthetic(
PpTokenKind::Punct(Punct::Comma),
None,
TokenFlags::EMPTY,
hash,
));
}
let sym =
*spellings[byte as usize].get_or_insert_with(|| interner.intern(itoa(byte).as_str()));
let flags = if first { TokenFlags::EMPTY } else { TokenFlags::LEADING_SPACE };
out.push(Tok::synthetic(PpTokenKind::Number, Some(sym), flags, hash));
first = false;
}
if let Some((head, tail)) = params.suffix.split_first() {
let mut head = *head;
head.flags = head.flags.with(TokenFlags::LEADING_SPACE);
out.push(head);
out.extend(tail.iter().copied());
}
opens_the_line(out, began);
}
fn opens_the_line(out: &mut [Tok], began: usize) {
if let Some(first) = out.get_mut(began) {
first.flags = first.flags.with(TokenFlags::START_OF_LINE);
}
}
struct Decimal {
digits: [u8; 3],
from: usize,
}
impl Decimal {
fn as_str(&self) -> &str {
std::str::from_utf8(&self.digits[self.from..]).unwrap_or("0")
}
}
fn itoa(mut byte: u8) -> Decimal {
let mut digits = [b'0'; 3];
let mut from = 3;
loop {
from -= 1;
digits[from] = b'0' + byte % 10;
byte /= 10;
if byte == 0 {
return Decimal { digits, from };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_byte_is_written_in_decimal_without_leading_zeros() {
assert_eq!(itoa(0).as_str(), "0");
assert_eq!(itoa(7).as_str(), "7");
assert_eq!(itoa(10).as_str(), "10");
assert_eq!(itoa(99).as_str(), "99");
assert_eq!(itoa(100).as_str(), "100");
assert_eq!(itoa(255).as_str(), "255");
}
#[test]
fn the_offset_is_applied_before_the_limit() {
let params = Params { limit: Some(4), offset: 4, ..Params::default() };
assert_eq!(params.taken(8), 4);
assert_eq!(params.taken(6), 2);
assert_eq!(params.taken(2), 0);
}
#[test]
fn no_limit_is_not_a_limit_of_zero() {
let params = Params::default();
assert_eq!(params.taken(9), 9);
let none_left = Params { limit: Some(0), ..Params::default() };
assert_eq!(none_left.taken(9), 0);
}
}