use super::intern::{SymId, intern, is_canonical_id, lookup_interned, resolve_sym};
use super::value::{Value, ValueKind};
use crate::gc_trace::GcTrace;
use rustc_hash::FxHashMap;
#[derive(Clone, Debug)]
pub enum SymbolValue {
Plain(Option<Value>),
Alias(SymId),
BufferLocal {
default: Option<Value>,
local_if_set: bool,
},
Forwarded,
}
impl Default for SymbolValue {
fn default() -> Self {
SymbolValue::Plain(None)
}
}
#[derive(Clone, Debug)]
pub struct SymbolData {
pub name: SymId,
pub value: SymbolValue,
pub function: Option<Value>,
pub plist: FxHashMap<SymId, Value>,
pub special: bool,
pub constant: bool,
interned_global: bool,
function_unbound: bool,
}
impl SymbolData {
pub fn new(name: SymId) -> Self {
Self {
name,
value: SymbolValue::Plain(None),
function: None,
plist: FxHashMap::default(),
special: false,
constant: false,
interned_global: false,
function_unbound: false,
}
}
}
#[derive(Clone, Debug)]
pub struct Obarray {
symbols: Vec<Option<SymbolData>>,
global_member_count: usize,
function_epoch: u64,
}
impl Default for Obarray {
fn default() -> Self {
Self::new()
}
}
impl Obarray {
fn is_canonical_symbol_id(id: SymId) -> bool {
is_canonical_id(id)
}
fn slot_index(id: SymId) -> usize {
id.0 as usize
}
fn slot(&self, id: SymId) -> Option<&SymbolData> {
self.symbols
.get(Self::slot_index(id))
.and_then(Option::as_ref)
}
fn slot_mut(&mut self, id: SymId) -> Option<&mut SymbolData> {
self.symbols
.get_mut(Self::slot_index(id))
.and_then(Option::as_mut)
}
fn ensure_slot(&mut self, id: SymId) -> &mut SymbolData {
let idx = Self::slot_index(id);
if self.symbols.len() <= idx {
self.symbols.resize_with(idx + 1, || None);
}
self.symbols[idx].get_or_insert_with(|| SymbolData::new(id))
}
fn mark_global_member(&mut self, id: SymId) {
let added = {
let sym = self.ensure_slot(id);
if sym.interned_global {
return;
}
sym.interned_global = true;
let name = resolve_sym(id);
if name.starts_with(':') {
sym.special = true;
sym.constant = true;
if matches!(sym.value, SymbolValue::Plain(None)) {
sym.value = SymbolValue::Plain(Some(Value::keyword_id(id)));
}
}
true
};
if added {
self.global_member_count += 1;
}
}
fn clear_global_member(&mut self, id: SymId) -> bool {
let Some(sym) = self.slot_mut(id) else {
return false;
};
if !sym.interned_global {
return false;
}
sym.interned_global = false;
self.global_member_count = self.global_member_count.saturating_sub(1);
true
}
fn ensure_global_member_if_canonical(&mut self, id: SymId) {
if Self::is_canonical_symbol_id(id) {
self.mark_global_member(id);
}
}
fn is_global_member(&self, id: SymId) -> bool {
self.slot(id).is_some_and(|sym| sym.interned_global)
}
fn value_from_symbol_id(&self, id: SymId) -> Value {
let name = resolve_sym(id);
if self.is_global_member(id) {
if name == "nil" {
return Value::NIL;
}
if name == "t" {
return Value::T;
}
if name.starts_with(':') {
return Value::keyword_id(id);
}
}
Value::symbol(id)
}
pub fn new() -> Self {
let mut ob = Self {
symbols: Vec::new(),
global_member_count: 0,
function_epoch: 0,
};
let t_id = intern("t");
{
let t_sym = ob.ensure_slot(t_id);
t_sym.value = SymbolValue::Plain(Some(Value::T));
t_sym.constant = true;
t_sym.special = true;
}
ob.mark_global_member(t_id);
let nil_id = intern("nil");
{
let nil_sym = ob.ensure_slot(nil_id);
nil_sym.value = SymbolValue::Plain(Some(Value::NIL));
nil_sym.constant = true;
nil_sym.special = true;
}
ob.mark_global_member(nil_id);
ob
}
pub fn intern(&mut self, name: &str) -> String {
let id = intern(name);
self.ensure_symbol_id(id);
self.mark_global_member(id);
name.to_string()
}
pub fn ensure_interned_global_id(&mut self, id: SymId) {
self.ensure_global_member_if_canonical(id);
}
pub fn intern_soft(&self, name: &str) -> Option<&SymbolData> {
let id = lookup_interned(name)?;
self.slot(id).filter(|sym| sym.interned_global)
}
pub fn get_or_intern(&mut self, name: &str) -> &mut SymbolData {
let id = intern(name);
self.mark_global_member(id);
self.ensure_symbol_id(id)
}
pub fn get(&self, name: &str) -> Option<&SymbolData> {
let id = lookup_interned(name)?;
self.slot(id).filter(|sym| sym.interned_global)
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut SymbolData> {
let id = lookup_interned(name)?;
self.slot_mut(id).filter(|sym| sym.interned_global)
}
pub fn ensure_symbol_id(&mut self, id: SymId) -> &mut SymbolData {
self.ensure_slot(id)
}
pub fn get_by_id(&self, id: SymId) -> Option<&SymbolData> {
self.slot(id)
}
pub fn get_mut_by_id(&mut self, id: SymId) -> Option<&mut SymbolData> {
self.slot_mut(id)
}
pub fn symbol_value(&self, name: &str) -> Option<&Value> {
self.symbol_value_id(intern(name))
}
pub fn symbol_value_id(&self, id: SymId) -> Option<&Value> {
let mut current = id;
for _ in 0..50 {
match self.slot(current)?.value {
SymbolValue::Plain(ref v) => return v.as_ref(),
SymbolValue::Alias(target) => current = target,
SymbolValue::BufferLocal { ref default, .. } => return default.as_ref(),
SymbolValue::Forwarded => return None,
}
}
None }
pub fn set_symbol_value(&mut self, name: &str, value: Value) {
let id = intern(name);
self.mark_global_member(id);
self.set_symbol_value_id_inner(id, value);
}
pub fn set_symbol_value_id(&mut self, id: SymId, value: Value) {
self.ensure_global_member_if_canonical(id);
self.set_symbol_value_id_inner(id, value);
}
fn set_symbol_value_id_inner(&mut self, id: SymId, value: Value) {
let target = self.resolve_alias_for_write(id);
let sym = self.ensure_symbol_id(target);
match sym.value {
SymbolValue::Plain(_) => sym.value = SymbolValue::Plain(Some(value)),
SymbolValue::BufferLocal {
ref mut default, ..
} => *default = Some(value),
SymbolValue::Forwarded => { }
SymbolValue::Alias(_) => {
sym.value = SymbolValue::Plain(Some(value));
}
}
}
pub fn for_each_value_cell_mut(&mut self, mut f: impl FnMut(&mut Value)) {
for sym in self.symbols.iter_mut().flatten() {
match &mut sym.value {
SymbolValue::Plain(Some(value)) => f(value),
SymbolValue::BufferLocal {
default: Some(value),
..
} => f(value),
SymbolValue::Plain(None)
| SymbolValue::BufferLocal { default: None, .. }
| SymbolValue::Alias(_)
| SymbolValue::Forwarded => {}
}
}
}
fn resolve_alias_for_write(&mut self, id: SymId) -> SymId {
let mut current = id;
for _ in 0..50 {
match self.slot(current) {
Some(s) => match s.value {
SymbolValue::Alias(target) => current = target,
_ => return current,
},
None => return current,
}
}
current }
pub fn symbol_function(&self, name: &str) -> Option<&Value> {
self.symbol_function_id(intern(name))
}
pub fn symbol_function_id(&self, id: SymId) -> Option<&Value> {
let sym = self.slot(id)?;
if sym.function_unbound {
return None;
}
sym.function.as_ref()
}
pub fn symbol_function_of_value(&self, value: &Value) -> Option<&Value> {
match value.kind() {
ValueKind::Symbol(id) => self.symbol_function_id(id),
ValueKind::Nil => self.symbol_function("nil"),
ValueKind::T => self.symbol_function("t"),
_ => None,
}
}
pub fn set_symbol_function(&mut self, name: &str, function: Value) {
let id = intern(name);
self.mark_global_member(id);
let sym = self.ensure_symbol_id(id);
sym.function = Some(function);
sym.function_unbound = false;
self.function_epoch = self.function_epoch.wrapping_add(1);
}
pub fn set_symbol_function_id(&mut self, id: SymId, function: Value) {
self.ensure_global_member_if_canonical(id);
let sym = self.ensure_symbol_id(id);
sym.function = Some(function);
sym.function_unbound = false;
self.function_epoch = self.function_epoch.wrapping_add(1);
}
pub fn fmakunbound(&mut self, name: &str) {
self.fmakunbound_id(intern(name));
}
pub fn fmakunbound_id(&mut self, id: SymId) {
self.ensure_global_member_if_canonical(id);
let sym = self.ensure_symbol_id(id);
let mut changed = !sym.function_unbound;
sym.function_unbound = true;
changed |= sym.function.take().is_some();
if changed {
self.function_epoch = self.function_epoch.wrapping_add(1);
}
}
pub fn clear_function_silent(&mut self, name: &str) {
self.clear_function_silent_id(intern(name));
}
pub fn clear_function_silent_id(&mut self, id: SymId) {
if let Some(sym) = self.slot_mut(id) {
if sym.function.take().is_some() {
self.function_epoch = self.function_epoch.wrapping_add(1);
}
}
}
pub fn makunbound(&mut self, name: &str) {
self.makunbound_id(intern(name));
}
pub fn makunbound_id(&mut self, id: SymId) {
self.ensure_global_member_if_canonical(id);
let target = self.resolve_alias_for_write(id);
if let Some(sym) = self.slot_mut(target) {
if !sym.constant {
match sym.value {
SymbolValue::Plain(_) => sym.value = SymbolValue::Plain(None),
SymbolValue::BufferLocal {
ref mut default, ..
} => *default = None,
SymbolValue::Forwarded => { }
SymbolValue::Alias(_) => sym.value = SymbolValue::Plain(None),
}
}
}
}
pub fn boundp(&self, name: &str) -> bool {
self.boundp_id(intern(name))
}
pub fn boundp_id(&self, id: SymId) -> bool {
let mut current = id;
for _ in 0..50 {
match self.slot(current) {
Some(s) => match &s.value {
SymbolValue::Plain(v) => return v.is_some(),
SymbolValue::Alias(target) => current = *target,
SymbolValue::BufferLocal { default, .. } => return default.is_some(),
SymbolValue::Forwarded => return false,
},
None => return false,
}
}
false }
pub fn fboundp(&self, name: &str) -> bool {
self.fboundp_id(intern(name))
}
pub fn fboundp_id(&self, id: SymId) -> bool {
self.slot(id)
.filter(|sym| !sym.function_unbound)
.and_then(|s| s.function.as_ref())
.is_some_and(|f| !f.is_nil())
}
pub fn get_property(&self, name: &str, prop: &str) -> Option<&Value> {
self.get_property_id(intern(name), intern(prop))
}
pub fn get_property_id(&self, symbol: SymId, prop: SymId) -> Option<&Value> {
self.slot(symbol).and_then(|s| s.plist.get(&prop))
}
pub fn put_property(&mut self, name: &str, prop: &str, value: Value) {
let symbol = intern(name);
self.mark_global_member(symbol);
let sym = self.ensure_symbol_id(symbol);
sym.plist.insert(intern(prop), value);
}
pub fn put_property_id(&mut self, symbol: SymId, prop: SymId, value: Value) {
self.ensure_global_member_if_canonical(symbol);
let sym = self.ensure_symbol_id(symbol);
sym.plist.insert(prop, value);
}
pub fn replace_symbol_plist_id<I>(&mut self, symbol: SymId, entries: I)
where
I: IntoIterator<Item = (SymId, Value)>,
{
self.ensure_global_member_if_canonical(symbol);
let sym = self.ensure_symbol_id(symbol);
sym.plist.clear();
sym.plist.extend(entries);
}
pub fn symbol_plist(&self, name: &str) -> Value {
self.symbol_plist_id(intern(name))
}
pub fn symbol_plist_id(&self, id: SymId) -> Value {
match self.slot(id) {
Some(sym) if !sym.plist.is_empty() => {
let mut items = Vec::new();
for (k, v) in &sym.plist {
items.push(self.value_from_symbol_id(*k));
items.push(*v);
}
Value::list(items)
}
_ => Value::NIL,
}
}
pub fn make_special(&mut self, name: &str) {
let id = intern(name);
self.mark_global_member(id);
self.ensure_symbol_id(id).special = true;
}
pub fn make_special_id(&mut self, id: SymId) {
self.ensure_global_member_if_canonical(id);
self.ensure_symbol_id(id).special = true;
}
pub fn make_non_special(&mut self, name: &str) {
let id = intern(name);
self.mark_global_member(id);
self.ensure_symbol_id(id).special = false;
}
pub fn make_non_special_id(&mut self, id: SymId) {
self.ensure_global_member_if_canonical(id);
self.ensure_symbol_id(id).special = false;
}
pub fn is_special(&self, name: &str) -> bool {
self.is_special_id(intern(name))
}
pub fn is_special_id(&self, id: SymId) -> bool {
self.slot(id).is_some_and(|s| s.special)
}
pub fn is_constant(&self, name: &str) -> bool {
self.is_constant_id(intern(name))
}
pub fn is_constant_id(&self, id: SymId) -> bool {
(Self::is_canonical_symbol_id(id) && resolve_sym(id).starts_with(':'))
|| self.slot(id).is_some_and(|s| s.constant)
}
pub fn set_constant(&mut self, name: &str) {
let id = intern(name);
self.set_constant_id(id);
}
pub fn set_constant_id(&mut self, id: SymId) {
self.ensure_global_member_if_canonical(id);
self.ensure_symbol_id(id).constant = true;
}
pub fn make_buffer_local(&mut self, name: &str, local_if_set: bool) {
let id = intern(name);
self.mark_global_member(id);
let sym = self.ensure_symbol_id(id);
let old_default = match &sym.value {
SymbolValue::Plain(v) => v.clone(),
SymbolValue::BufferLocal { default, .. } => default.clone(),
_ => None,
};
sym.value = SymbolValue::BufferLocal {
default: old_default,
local_if_set,
};
}
pub fn make_alias(&mut self, id: SymId, target: SymId) {
let sym = self.ensure_symbol_id(id);
sym.value = SymbolValue::Alias(target);
}
pub fn is_buffer_local(&self, name: &str) -> bool {
self.is_buffer_local_id(intern(name))
}
pub fn is_buffer_local_id(&self, id: SymId) -> bool {
self.slot(id)
.is_some_and(|s| matches!(s.value, SymbolValue::BufferLocal { .. }))
}
pub fn is_alias_id(&self, id: SymId) -> bool {
self.slot(id)
.is_some_and(|s| matches!(s.value, SymbolValue::Alias(_)))
}
pub fn default_value_id(&self, id: SymId) -> Option<&Value> {
let mut current = id;
for _ in 0..50 {
match self.slot(current)?.value {
SymbolValue::Plain(ref v) => return v.as_ref(),
SymbolValue::BufferLocal { ref default, .. } => return default.as_ref(),
SymbolValue::Alias(target) => current = target,
SymbolValue::Forwarded => return None,
}
}
None
}
pub fn indirect_function(&self, name: &str) -> Option<Value> {
self.indirect_function_id(intern(name))
}
pub fn indirect_function_id(&self, id: SymId) -> Option<Value> {
let mut current_id = id;
let mut depth = 0;
loop {
if depth > 100 {
return None; }
let func = self.slot(current_id)?.function.as_ref()?;
match func.kind() {
ValueKind::Symbol(id) => {
current_id = id;
depth += 1;
}
_ => return Some(*func),
}
}
}
pub fn len(&self) -> usize {
self.global_member_count
}
pub fn is_empty(&self) -> bool {
self.global_member_count == 0
}
pub fn all_symbols(&self) -> Vec<&str> {
self.symbols
.iter()
.flatten()
.filter(|sym| sym.interned_global)
.map(|sym| resolve_sym(sym.name))
.collect()
}
pub fn unintern(&mut self, name: &str) -> bool {
let id = intern(name);
let removed_symbol = self.clear_global_member(id);
if removed_symbol {
self.function_epoch = self.function_epoch.wrapping_add(1);
}
removed_symbol
}
pub fn function_epoch(&self) -> u64 {
self.function_epoch
}
pub fn is_function_unbound(&self, name: &str) -> bool {
self.is_function_unbound_id(intern(name))
}
pub fn is_function_unbound_id(&self, id: SymId) -> bool {
self.slot(id).is_some_and(|sym| sym.function_unbound)
}
pub(crate) fn iter_symbols(&self) -> impl Iterator<Item = (SymId, &SymbolData)> {
self.symbols.iter().enumerate().filter_map(|(idx, slot)| {
debug_assert!(idx <= u32::MAX as usize, "symbol index overflow");
slot.as_ref().map(|sym| (SymId(idx as u32), sym))
})
}
pub(crate) fn global_member_ids(&self) -> impl Iterator<Item = SymId> + '_ {
self.iter_symbols()
.filter(|(_, sym)| sym.interned_global)
.map(|(id, _)| id)
}
pub(crate) fn function_unbound_ids(&self) -> impl Iterator<Item = SymId> + '_ {
self.iter_symbols()
.filter(|(_, sym)| sym.function_unbound)
.map(|(id, _)| id)
}
pub(crate) fn from_dump(
symbols: Vec<(SymId, SymbolData)>,
global_members: Vec<SymId>,
function_unbound: Vec<SymId>,
function_epoch: u64,
) -> Self {
let mut ob = Self {
symbols: Vec::new(),
global_member_count: 0,
function_epoch,
};
for (id, mut sym) in symbols {
sym.interned_global = false;
sym.function_unbound = false;
*ob.ensure_slot(id) = sym;
}
for id in global_members {
ob.mark_global_member(id);
}
for id in function_unbound {
ob.ensure_slot(id).function_unbound = true;
}
ob
}
}
impl GcTrace for Obarray {
fn trace_roots(&self, roots: &mut Vec<Value>) {
for sym in self.symbols.iter().flatten() {
match &sym.value {
SymbolValue::Plain(Some(v)) => roots.push(*v),
SymbolValue::BufferLocal {
default: Some(v), ..
} => roots.push(*v),
SymbolValue::Plain(None)
| SymbolValue::BufferLocal { default: None, .. }
| SymbolValue::Alias(_)
| SymbolValue::Forwarded => {}
}
if let Some(ref f) = sym.function {
roots.push(*f);
}
for pval in sym.plist.values() {
roots.push(*pval);
}
}
}
}
#[cfg(test)]
#[path = "symbol_test.rs"]
mod tests;