use std::path::{Path, PathBuf};
use rucc_base::Interner;
use rucc_diag::{BytePos, Diagnostic, SourceMap, Span};
use rucc_lex::{Lexer, Options, PpToken, PpTokenKind, TokenFlags};
use rucc_session::{FileSystem, SearchPath};
use crate::token::Tok;
pub struct Context<'a> {
pub interner: &'a mut Interner,
pub sources: &'a mut SourceMap,
pub fs: &'a dyn FileSystem,
pub search: &'a SearchPath,
pub lex: Options,
pub max_include_depth: u32,
}
impl<'a> Context<'a> {
pub fn new(
interner: &'a mut Interner,
sources: &'a mut SourceMap,
fs: &'a dyn FileSystem,
search: &'a SearchPath,
) -> Context<'a> {
Context { interner, sources, fs, search, lex: Options::new(), max_include_depth: 200 }
}
}
impl std::fmt::Debug for Context<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Context")
.field("lex", &self.lex)
.field("max_include_depth", &self.max_include_depth)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub(crate) struct Frame {
pub(crate) at: Span,
pub(crate) path: PathBuf,
pub(crate) dir: Option<PathBuf>,
pub(crate) next: usize,
}
pub(crate) struct Reader<'a> {
lexer: Lexer<'a>,
pending: Option<PpToken>,
}
impl<'a> Reader<'a> {
pub(crate) fn new(src: &'a [u8], start: BytePos, opts: Options) -> Reader<'a> {
Reader { lexer: Lexer::new(src, start, opts), pending: None }
}
pub(crate) fn next(&mut self, interner: &mut Interner) -> PpToken {
match self.pending.take() {
Some(token) => token,
None => self.lexer.next_token(interner),
}
}
pub(crate) fn put_back(&mut self, token: PpToken) {
self.pending = Some(token);
}
pub(crate) fn line(&mut self, interner: &mut Interner, out: &mut Vec<PpToken>) {
loop {
let token = self.next(interner);
if token.is_eof() || token.flags.has(TokenFlags::START_OF_LINE) {
self.put_back(token);
return;
}
out.push(token);
}
}
pub(crate) fn header_name(&mut self, interner: &mut Interner) -> Option<PpToken> {
debug_assert!(self.pending.is_none(), "the header name has to be asked for first");
self.lexer.header_name(interner)
}
pub(crate) fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
self.lexer.take_diagnostics()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Header {
pub(crate) name: String,
pub(crate) angled: bool,
}
pub(crate) fn header_from_token(spelling: &str) -> Option<Header> {
let angled = spelling.starts_with('<');
let close = if angled { '>' } else { '"' };
let inner = spelling.strip_prefix(if angled { '<' } else { '"' })?;
let inner = inner.strip_suffix(close).unwrap_or(inner);
if inner.is_empty() {
return None;
}
Some(Header { name: inner.to_owned(), angled })
}
pub(crate) fn header_from_tokens(spellings: &[&str]) -> Option<Header> {
let first = *spellings.first()?;
if first.starts_with('"') && spellings.len() == 1 {
return header_from_token(first);
}
if first != "<" {
return None;
}
let close = spellings.iter().rposition(|s| *s == ">")?;
if close < 2 {
return None;
}
let name: String = spellings[1..close].concat();
Some(Header { name, angled: true })
}
pub(crate) const UNKNOWN: &str = "<unknown>";
pub(crate) fn quoted(name: &str) -> String {
let mut out = String::with_capacity(name.len() + 2);
out.push('"');
for ch in name.chars() {
if ch == '\\' || ch == '"' {
out.push('\\');
}
out.push(ch);
}
out.push('"');
out
}
pub(crate) fn base_name(name: &str) -> &str {
match name.rfind(['/', '\\']) {
Some(at) => &name[at + 1..],
None => name,
}
}
pub(crate) fn directory_of(name: &str) -> Option<PathBuf> {
Path::new(name).parent().map(Path::to_path_buf)
}
pub(crate) fn spelling(token: Tok, interner: &Interner) -> &str {
match token.kind {
PpTokenKind::Punct(p) => p.as_str(),
_ => token.value.map_or("", |v| interner.resolve(v)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_header_name_keeps_everything_between_the_delimiters() {
assert_eq!(
header_from_token("<sys/types.h>"),
Some(Header { name: "sys/types.h".to_owned(), angled: true })
);
assert_eq!(
header_from_token("\"local.h\""),
Some(Header { name: "local.h".to_owned(), angled: false })
);
}
#[test]
fn a_backslash_in_a_header_name_is_a_backslash() {
let header = header_from_token("\"win32\\types.h\"").unwrap();
assert_eq!(header.name, "win32\\types.h");
}
#[test]
fn an_empty_header_name_is_not_a_header_name() {
assert_eq!(header_from_token("<>"), None);
assert_eq!(header_from_token("\"\""), None);
}
#[test]
fn a_computed_include_concatenates_the_spellings() {
let header = header_from_tokens(&["<", "sys", "/", "types", ".", "h", ">"]).unwrap();
assert_eq!(header.name, "sys/types.h");
assert!(header.angled);
}
#[test]
fn a_computed_include_can_expand_to_a_string_literal() {
let header = header_from_tokens(&["\"local.h\""]).unwrap();
assert_eq!(header.name, "local.h");
assert!(!header.angled);
}
#[test]
fn a_computed_include_that_is_neither_is_refused() {
assert_eq!(header_from_tokens(&[]), None);
assert_eq!(header_from_tokens(&["1"]), None);
assert_eq!(header_from_tokens(&["<", "a"]), None);
assert_eq!(header_from_tokens(&["<", ">"]), None);
}
#[test]
fn the_last_angle_bracket_closes_the_name() {
let header = header_from_tokens(&["<", "a", ">", "b", ">"]).unwrap();
assert_eq!(header.name, "a>b");
}
}