use std::collections::HashMap;
use std::fmt;
use crate::index::Idx;
#[derive(Debug)]
pub struct SymbolTable;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Symbol(Idx<SymbolTable>);
impl Symbol {
#[inline]
pub const fn raw(self) -> u32 {
self.0.raw()
}
#[inline]
#[must_use]
pub const fn from_raw(raw: u32) -> Symbol {
Symbol(Idx::new(raw))
}
}
pub const RESERVED: &[&str] = &[
"__va_list_tag",
"gp_offset",
"fp_offset",
"overflow_arg_area",
"reg_save_area",
"__va_list",
"__stack",
"__gr_top",
"__vr_top",
"__gr_offs",
"__vr_offs",
];
pub mod sym {
use super::Symbol;
pub const VA_LIST_TAG: Symbol = Symbol::from_raw(0);
pub const GP_OFFSET: Symbol = Symbol::from_raw(1);
pub const FP_OFFSET: Symbol = Symbol::from_raw(2);
pub const OVERFLOW_ARG_AREA: Symbol = Symbol::from_raw(3);
pub const REG_SAVE_AREA: Symbol = Symbol::from_raw(4);
pub const VA_LIST: Symbol = Symbol::from_raw(5);
pub const STACK: Symbol = Symbol::from_raw(6);
pub const GR_TOP: Symbol = Symbol::from_raw(7);
pub const VR_TOP: Symbol = Symbol::from_raw(8);
pub const GR_OFFS: Symbol = Symbol::from_raw(9);
pub const VR_OFFS: Symbol = Symbol::from_raw(10);
}
pub struct Interner {
buf: String,
spans: Vec<(u32, u32)>,
map: HashMap<Box<str>, Symbol>,
raw: HashMap<Symbol, Box<[u8]>>,
raw_map: HashMap<Box<[u8]>, Symbol>,
}
impl Default for Interner {
fn default() -> Self {
Self::new()
}
}
impl Interner {
pub fn new() -> Self {
Self::with_capacity(RESERVED.len())
}
pub fn with_capacity(cap: usize) -> Self {
let cap = cap.max(RESERVED.len());
let mut interner = Self {
buf: String::with_capacity(cap * 8),
spans: Vec::with_capacity(cap),
map: HashMap::with_capacity(cap),
raw: HashMap::new(),
raw_map: HashMap::new(),
};
for name in RESERVED {
interner.intern(name);
}
interner
}
pub fn intern(&mut self, s: &str) -> Symbol {
if let Some(&sym) = self.map.get(s) {
return sym;
}
let start = u32::try_from(self.buf.len()).expect("interner buffer overflow");
self.buf.push_str(s);
let end = u32::try_from(self.buf.len()).expect("interner buffer overflow");
let sym = Symbol(Idx::from_usize(self.spans.len()));
self.spans.push((start, end));
self.map.insert(s.into(), sym);
sym
}
pub fn intern_bytes(&mut self, bytes: &[u8]) -> Symbol {
if let Ok(text) = std::str::from_utf8(bytes) {
return self.intern(text);
}
if let Some(&sym) = self.raw_map.get(bytes) {
return sym;
}
let lossy = String::from_utf8_lossy(bytes);
let start = u32::try_from(self.buf.len()).expect("interner buffer overflow");
self.buf.push_str(&lossy);
let end = u32::try_from(self.buf.len()).expect("interner buffer overflow");
let sym = Symbol(Idx::from_usize(self.spans.len()));
self.spans.push((start, end));
self.raw.insert(sym, bytes.into());
self.raw_map.insert(bytes.into(), sym);
sym
}
pub fn resolve(&self, sym: Symbol) -> &str {
let (start, end) = self.spans[sym.0.index()];
&self.buf[start as usize..end as usize]
}
pub fn resolve_bytes(&self, sym: Symbol) -> &[u8] {
match self.raw.get(&sym) {
Some(bytes) => bytes,
None => self.resolve(sym).as_bytes(),
}
}
pub fn len(&self) -> usize {
self.spans.len()
}
pub fn is_empty(&self) -> bool {
self.spans.len() <= RESERVED.len()
}
pub fn bytes(&self) -> usize {
self.buf.len()
}
}
impl fmt::Debug for Interner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Interner")
.field("symbols", &self.spans.len())
.field("bytes", &self.buf.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_same_string_gets_the_same_symbol() {
let mut i = Interner::new();
let before = i.len();
let a = i.intern("static_assert");
let b = i.intern("static_assert");
assert_eq!(a, b);
assert_eq!(i.len() - before, 1);
}
#[test]
fn different_strings_get_different_symbols() {
let mut i = Interner::new();
let before = i.len();
assert_ne!(i.intern("int"), i.intern("long"));
assert_eq!(i.len() - before, 2);
}
#[test]
fn the_reserved_names_are_there_before_anything_is_read() {
let i = Interner::new();
assert_eq!(i.len(), RESERVED.len());
assert!(i.is_empty(), "the reserved names do not count as something having been read");
assert_eq!(i.resolve(sym::VA_LIST_TAG), "__va_list_tag");
assert_eq!(i.resolve(sym::GP_OFFSET), "gp_offset");
assert_eq!(i.resolve(sym::FP_OFFSET), "fp_offset");
assert_eq!(i.resolve(sym::OVERFLOW_ARG_AREA), "overflow_arg_area");
assert_eq!(i.resolve(sym::REG_SAVE_AREA), "reg_save_area");
assert_eq!(i.resolve(sym::VA_LIST), "__va_list");
assert_eq!(i.resolve(sym::STACK), "__stack");
assert_eq!(i.resolve(sym::GR_TOP), "__gr_top");
assert_eq!(i.resolve(sym::VR_TOP), "__vr_top");
assert_eq!(i.resolve(sym::GR_OFFS), "__gr_offs");
assert_eq!(i.resolve(sym::VR_OFFS), "__vr_offs");
}
#[test]
fn a_reserved_name_written_in_the_source_is_the_symbol_it_already_had() {
let mut i = Interner::new();
let before = i.len();
assert_eq!(i.intern("__va_list_tag"), sym::VA_LIST_TAG);
assert_eq!(i.len(), before);
}
#[test]
fn every_interner_agrees_on_where_the_reserved_names_are() {
let small = Interner::new();
let large = Interner::with_capacity(4096);
for (at, name) in RESERVED.iter().enumerate() {
let sym = Symbol::from_raw(u32::try_from(at).expect("eleven names fit in a u32"));
assert_eq!(small.resolve(sym), *name);
assert_eq!(large.resolve(sym), *name);
}
}
#[test]
fn resolves_back_to_the_text() {
let mut i = Interner::new();
let s = i.intern("__builtin_constant_p");
assert_eq!(i.resolve(s), "__builtin_constant_p");
}
#[test]
fn symbols_are_numbered_in_allocation_order() {
let mut i = Interner::new();
let first = i.intern("a");
let second = i.intern("b");
assert!(first < second, "symbol order must be allocation order, not hash order");
}
#[test]
fn the_empty_string_is_internable() {
let mut i = Interner::new();
let before = i.bytes();
let s = i.intern("");
assert_eq!(i.resolve(s), "");
assert_eq!(i.bytes(), before);
}
#[test]
fn a_spelling_that_is_text_is_the_same_symbol_however_it_was_interned() {
let mut i = Interner::new();
let text = i.intern("hello");
assert_eq!(i.intern_bytes(b"hello"), text);
assert_eq!(i.resolve_bytes(text), b"hello");
}
#[test]
fn a_spelling_that_is_not_text_keeps_its_bytes() {
let mut i = Interner::new();
let raw = i.intern_bytes(b"\"\xff\"");
assert_eq!(i.resolve_bytes(raw), b"\"\xff\"");
assert_eq!(i.intern_bytes(b"\"\xff\""), raw, "interning it twice is one symbol");
assert_eq!(i.resolve(raw), "\"\u{fffd}\"");
}
#[test]
fn two_spellings_that_read_the_same_lossily_are_still_two_symbols() {
let mut i = Interner::new();
let one = i.intern_bytes(b"\xff");
let other = i.intern_bytes(b"\xfe");
assert_eq!(i.resolve(one), i.resolve(other), "both read as the replacement character");
assert_ne!(one, other, "the bytes differ, so the spellings do");
assert_eq!(i.resolve_bytes(one), b"\xff");
assert_eq!(i.resolve_bytes(other), b"\xfe");
assert_ne!(i.intern("\u{fffd}"), one);
}
#[test]
fn a_symbol_is_four_bytes() {
assert_eq!(size_of::<Symbol>(), 4);
assert_eq!(size_of::<Option<Symbol>>(), 4);
}
}