use std::collections::{HashMap, HashSet};
use std::fmt;
use std::rc::Rc;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicU64, Ordering};
use super::intern::{SymId, intern, resolve_sym};
use crate::buffer::text_props::TextPropertyTable;
use crate::gc_trace::GcTrace;
use crate::heap_types::LispString;
use crate::tagged::gc::with_tagged_heap;
use crate::tagged::header::{
BufferObj, ByteCodeObj, FloatObj, FrameObj, HashTableObj, LambdaObj, LispValueSlice, MacroObj,
MarkerObj, OverlayObj, RecordObj, StringObj, TimerObj, VecLikeHeader, VectorObj, WindowObj,
};
use crate::tagged::mutate;
use crate::tagged::value::TaggedValue;
pub type Value = TaggedValue;
pub use crate::tagged::header::VecLikeType;
pub use crate::tagged::value::ValueKind;
#[derive(Debug, Clone)]
pub struct OrderedSymMap {
entries: Vec<(SymId, Value)>,
}
impl PartialEq for OrderedSymMap {
fn eq(&self, other: &Self) -> bool {
if self.entries.len() != other.entries.len() {
return false;
}
self.entries
.iter()
.zip(other.entries.iter())
.all(|((k1, v1), (k2, v2))| k1 == k2 && eq_value(v1, v2))
}
}
impl OrderedSymMap {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn with_capacity(cap: usize) -> Self {
Self {
entries: Vec::with_capacity(cap),
}
}
pub fn get(&self, key: &SymId) -> Option<&Value> {
self.entries
.iter()
.rev()
.find(|(k, _)| k == key)
.map(|(_, v)| v)
}
pub fn insert(&mut self, key: SymId, value: Value) {
if let Some(entry) = self.entries.iter_mut().rev().find(|(k, _)| *k == key) {
entry.1 = value;
} else {
self.entries.push((key, value));
}
}
pub fn contains_key(&self, key: &SymId) -> bool {
self.entries.iter().any(|(k, _)| k == key)
}
pub fn values(&self) -> impl Iterator<Item = &Value> {
self.entries.iter().map(|(_, v)| v)
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Value> {
self.entries.iter_mut().map(|(_, v)| v)
}
pub fn iter(&self) -> impl Iterator<Item = (&SymId, &Value)> {
self.entries.iter().map(|(k, v)| (k, v))
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub(crate) fn from_entries(entries: Vec<(SymId, Value)>) -> Self {
Self { entries }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RuntimeBindingValue {
Bound(Value),
Void,
}
impl RuntimeBindingValue {
pub fn bound(value: Value) -> Self {
Self::Bound(value)
}
pub fn as_value(self) -> Option<Value> {
match self {
Self::Bound(value) => Some(value),
Self::Void => None,
}
}
pub fn as_ref(&self) -> Option<&Value> {
match self {
Self::Bound(value) => Some(value),
Self::Void => None,
}
}
}
#[derive(Debug, Clone)]
pub struct OrderedRuntimeBindingMap {
entries: Vec<(SymId, RuntimeBindingValue)>,
}
impl PartialEq for OrderedRuntimeBindingMap {
fn eq(&self, other: &Self) -> bool {
self.entries == other.entries
}
}
impl OrderedRuntimeBindingMap {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn with_capacity(cap: usize) -> Self {
Self {
entries: Vec::with_capacity(cap),
}
}
pub fn get(&self, key: &SymId) -> Option<&Value> {
self.get_binding(key).and_then(RuntimeBindingValue::as_ref)
}
pub fn get_binding(&self, key: &SymId) -> Option<&RuntimeBindingValue> {
self.entries
.iter()
.rev()
.find(|(k, _)| k == key)
.map(|(_, v)| v)
}
pub fn insert(&mut self, key: SymId, value: Value) {
self.insert_binding(key, RuntimeBindingValue::Bound(value));
}
pub fn insert_binding(&mut self, key: SymId, value: RuntimeBindingValue) {
if let Some(entry) = self.entries.iter_mut().rev().find(|(k, _)| *k == key) {
entry.1 = value;
} else {
self.entries.push((key, value));
}
}
pub fn set_void(&mut self, key: SymId) {
self.insert_binding(key, RuntimeBindingValue::Void);
}
pub fn contains_key(&self, key: &SymId) -> bool {
self.entries.iter().any(|(k, _)| k == key)
}
pub fn values(&self) -> impl Iterator<Item = &Value> {
self.entries.iter().filter_map(|(_, v)| v.as_ref())
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Value> {
self.entries.iter_mut().filter_map(|(_, v)| match v {
RuntimeBindingValue::Bound(value) => Some(value),
RuntimeBindingValue::Void => None,
})
}
pub fn iter(&self) -> impl Iterator<Item = (&SymId, &RuntimeBindingValue)> {
self.entries.iter().map(|(k, v)| (k, v))
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub(crate) fn from_entries(entries: Vec<(SymId, RuntimeBindingValue)>) -> Self {
Self { entries }
}
}
const ZERO_COUNT: u64 = 0;
static CONS_CELLS_CONSED: AtomicU64 = AtomicU64::new(ZERO_COUNT);
static FLOATS_CONSED: AtomicU64 = AtomicU64::new(ZERO_COUNT);
static VECTOR_CELLS_CONSED: AtomicU64 = AtomicU64::new(ZERO_COUNT);
static SYMBOLS_CONSED: AtomicU64 = AtomicU64::new(ZERO_COUNT);
static STRING_CHARS_CONSED: AtomicU64 = AtomicU64::new(ZERO_COUNT);
static INTERVALS_CONSED: AtomicU64 = AtomicU64::new(ZERO_COUNT);
static STRINGS_CONSED: AtomicU64 = AtomicU64::new(ZERO_COUNT);
fn add_wrapping(counter: &AtomicU64, delta: u64) {
counter.fetch_add(delta, Ordering::Relaxed);
}
fn as_neovm_int(value: u64) -> i64 {
value as i64
}
fn string_text_props(value: Value) -> Option<&'static TextPropertyTable> {
let ptr = value.as_string_ptr()? as *const StringObj;
Some(unsafe { &(*ptr).text_props })
}
pub fn reset_string_text_properties() {}
pub fn collect_string_text_prop_gc_roots(_roots: &mut Vec<Value>) {}
pub fn set_string_text_properties_table_for_value(value: Value, table: TextPropertyTable) {
let _ = mutate::with_string_text_props_mut(value, |props| {
*props = table;
});
}
pub fn set_string_text_properties_for_value(value: Value, runs: Vec<StringTextPropertyRun>) {
let mut table = TextPropertyTable::new();
for run in &runs {
if let Some(items) = list_to_vec(&run.plist) {
for chunk in items.chunks(2) {
if chunk.len() == 2 {
table.put_property(run.start, run.end, chunk[0], chunk[1]);
}
}
}
}
set_string_text_properties_table_for_value(value, table);
}
pub fn get_string_text_properties_for_value(value: Value) -> Option<Vec<StringTextPropertyRun>> {
let table = string_text_props(value)?;
if table.is_empty() {
return None;
}
let mut runs = Vec::new();
for interval in table.intervals_snapshot() {
if interval.properties.is_empty() {
continue;
}
let mut plist_items = Vec::new();
for (key, val) in interval.ordered_properties() {
plist_items.push(key);
plist_items.push(*val);
}
runs.push(StringTextPropertyRun {
start: interval.start,
end: interval.end,
plist: Value::list(plist_items),
});
}
if runs.is_empty() { None } else { Some(runs) }
}
pub fn string_has_text_properties_for_value(value: Value) -> bool {
string_text_props(value).is_some_and(|table| !table.is_empty())
}
pub fn get_string_text_properties_table_for_value(value: Value) -> Option<TextPropertyTable> {
let table = string_text_props(value)?;
if table.is_empty() {
None
} else {
Some(table.clone())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct StringTextPropertyRun {
pub start: usize,
pub end: usize,
pub plist: Value,
}
pub struct ConsSnapshot {
pub car: Value,
pub cdr: Value,
}
pub fn next_float_id() -> u32 {
0
}
#[derive(Clone, Debug)]
pub struct LambdaData {
pub params: LambdaParams,
pub body: Vec<Value>,
pub env: Option<Value>,
pub docstring: Option<LispString>,
pub doc_form: Option<Value>,
pub interactive: Option<Value>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LambdaParams {
pub required: Vec<SymId>,
pub optional: Vec<SymId>,
pub rest: Option<SymId>,
}
impl LambdaParams {
pub fn simple(names: Vec<SymId>) -> Self {
Self {
required: names,
optional: Vec::new(),
rest: None,
}
}
pub fn min_arity(&self) -> usize {
self.required.len()
}
pub fn max_arity(&self) -> Option<usize> {
if self.rest.is_some() {
None
} else {
Some(self.required.len() + self.optional.len())
}
}
}
use crate::tagged::header::{
CLOSURE_ARGLIST, CLOSURE_CODE, CLOSURE_CONSTANTS, CLOSURE_DOC_STRING, CLOSURE_INTERACTIVE,
CLOSURE_MIN_SLOTS, CLOSURE_STACK_DEPTH,
};
impl LambdaData {
pub fn to_closure_slots(&self) -> Vec<Value> {
let arglist = crate::emacs_core::builtins::lambda_params_to_value(&self.params);
let body = Value::list(self.body.clone());
let env = match self.env {
Some(env_val) if env_val.is_nil() => Value::list(vec![Value::T]),
Some(env_val) => env_val,
None => Value::NIL,
};
let depth = Value::NIL;
let doc = self
.doc_form
.or_else(|| {
self.docstring
.as_ref()
.map(|d| Value::heap_string(d.clone()))
})
.unwrap_or(Value::NIL);
let interactive = self.interactive.unwrap_or(Value::NIL);
vec![arglist, body, env, depth, doc, interactive]
}
}
#[derive(Clone, Debug)]
pub struct LispHashTable {
pub test: HashTableTest,
pub test_name: Option<SymId>,
pub size: i64,
pub weakness: Option<HashTableWeakness>,
pub rehash_size: f64,
pub rehash_threshold: f64,
pub data: HashMap<HashKey, Value>,
pub key_snapshots: HashMap<HashKey, Value>,
pub insertion_order: Vec<HashKey>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HashTableTest {
Eq,
Eql,
Equal,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HashTableWeakness {
Key,
Value,
KeyOrValue,
KeyAndValue,
}
#[derive(Clone, Debug)]
pub enum HashKey {
Nil,
True,
Int(i64),
Float(u64),
FloatEq(u64, u32),
Symbol(SymId),
Keyword(SymId),
Char(char),
Window(u64),
Frame(u64),
Ptr(usize),
EqualCons(Box<HashKey>, Box<HashKey>),
EqualVec(Vec<HashKey>),
SymbolWithPos(Box<HashKey>, Box<HashKey>),
Cycle(u32),
Text(String),
}
impl std::hash::Hash for HashKey {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let tag: u8 = match self {
HashKey::Nil => 0,
HashKey::True => 1,
HashKey::Int(_) => 2,
HashKey::Float(_) => 3,
HashKey::FloatEq(_, _) => 4,
HashKey::Symbol(_) => 5,
HashKey::Char(_) => 7,
HashKey::Window(_) => 8,
HashKey::Frame(_) => 9,
HashKey::Ptr(_) => 10,
HashKey::EqualCons(_, _) => 12,
HashKey::EqualVec(_) => 13,
HashKey::Keyword(_) => 14,
HashKey::Cycle(_) => 15,
HashKey::Text(_) => 16,
HashKey::SymbolWithPos(_, _) => 17,
};
tag.hash(state);
match self {
HashKey::Nil | HashKey::True => {}
HashKey::Int(n) => n.hash(state),
HashKey::Float(bits) => bits.hash(state),
HashKey::FloatEq(bits, id) => {
bits.hash(state);
id.hash(state);
}
HashKey::Symbol(id) | HashKey::Keyword(id) => id.hash(state),
HashKey::Char(c) => c.hash(state),
HashKey::Window(id) | HashKey::Frame(id) => id.hash(state),
HashKey::Ptr(p) => p.hash(state),
HashKey::EqualCons(car, cdr) => {
car.hash(state);
cdr.hash(state);
}
HashKey::EqualVec(items) => {
items.len().hash(state);
for item in items {
item.hash(state);
}
}
HashKey::SymbolWithPos(sym, pos) => {
sym.hash(state);
pos.hash(state);
}
HashKey::Cycle(index) => index.hash(state),
HashKey::Text(text) => text.hash(state),
}
}
}
impl PartialEq for HashKey {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(HashKey::Nil, HashKey::Nil) | (HashKey::True, HashKey::True) => true,
(HashKey::Int(a), HashKey::Int(b)) => a == b,
(HashKey::Float(a), HashKey::Float(b)) => a == b,
(HashKey::FloatEq(a, id_a), HashKey::FloatEq(b, id_b)) => a == b && id_a == id_b,
(HashKey::Symbol(a), HashKey::Symbol(b)) => a == b,
(HashKey::Keyword(a), HashKey::Keyword(b)) => a == b,
(HashKey::Char(a), HashKey::Char(b)) => a == b,
(HashKey::Window(a), HashKey::Window(b)) | (HashKey::Frame(a), HashKey::Frame(b)) => {
a == b
}
(HashKey::Ptr(a), HashKey::Ptr(b)) => a == b,
(HashKey::EqualCons(a_car, a_cdr), HashKey::EqualCons(b_car, b_cdr)) => {
a_car == b_car && a_cdr == b_cdr
}
(HashKey::EqualVec(a), HashKey::EqualVec(b)) => a == b,
(HashKey::SymbolWithPos(a_sym, a_pos), HashKey::SymbolWithPos(b_sym, b_pos)) => {
a_sym == b_sym && a_pos == b_pos
}
(HashKey::Cycle(a), HashKey::Cycle(b)) => a == b,
(HashKey::Text(a), HashKey::Text(b)) => a == b,
_ => false,
}
}
}
impl Eq for HashKey {}
impl HashKey {
pub fn from_str(s: impl Into<String>) -> Self {
HashKey::Text(s.into())
}
}
impl LispHashTable {
pub fn new(test: HashTableTest) -> Self {
Self::new_with_options(test, 0, None, 1.5, 0.8125)
}
pub fn new_with_options(
test: HashTableTest,
size: i64,
weakness: Option<HashTableWeakness>,
rehash_size: f64,
rehash_threshold: f64,
) -> Self {
Self {
test,
test_name: None,
size,
weakness,
rehash_size,
rehash_threshold,
data: HashMap::with_capacity(size.max(0) as usize),
key_snapshots: HashMap::with_capacity(size.max(0) as usize),
insertion_order: Vec::with_capacity(size.max(0) as usize),
}
}
}
pub(crate) fn build_hash_table_literal_value(
test: HashTableTest,
test_name: Option<SymId>,
size: i64,
weakness: Option<HashTableWeakness>,
rehash_size: f64,
rehash_threshold: f64,
entries: Vec<(Value, Value)>,
) -> Value {
let table_value =
Value::hash_table_with_options(test, size, weakness, rehash_size, rehash_threshold);
let _ = table_value.with_hash_table_mut(|table| {
table.test_name = test_name;
for (key_value, val_value) in entries {
let key = key_value.to_hash_key(&table.test);
let inserting_new_key = !table.data.contains_key(&key);
table.data.insert(key.clone(), val_value);
if inserting_new_key {
table.key_snapshots.insert(key.clone(), key_value);
table.insertion_order.push(key);
}
}
});
table_value
}
pub trait IntoSymbol {
fn into_symbol(self) -> Value;
}
impl IntoSymbol for SymId {
fn into_symbol(self) -> Value {
TaggedValue::from_sym_id(self)
}
}
impl IntoSymbol for &str {
fn into_symbol(self) -> Value {
if self == "nil" {
Value::NIL
} else if self == "t" {
Value::T
} else if self.starts_with(':') {
add_wrapping(&SYMBOLS_CONSED, 1);
TaggedValue::from_kw_id(intern(self))
} else {
add_wrapping(&SYMBOLS_CONSED, 1);
TaggedValue::from_sym_id(intern(self))
}
}
}
impl IntoSymbol for String {
fn into_symbol(self) -> Value {
self.as_str().into_symbol()
}
}
impl IntoSymbol for &String {
fn into_symbol(self) -> Value {
self.as_str().into_symbol()
}
}
impl IntoSymbol for &&str {
fn into_symbol(self) -> Value {
(*self).into_symbol()
}
}
impl IntoSymbol for &&String {
fn into_symbol(self) -> Value {
self.as_str().into_symbol()
}
}
fn canonical_keyword_name(name: &str) -> String {
if name.starts_with(':') {
name.to_owned()
} else {
format!(":{name}")
}
}
impl TaggedValue {
pub fn symbol(s: impl IntoSymbol) -> Self {
s.into_symbol()
}
pub fn make_symbol(s: impl AsRef<str>) -> Self {
s.as_ref().into_symbol()
}
pub fn keyword(s: impl AsRef<str>) -> Self {
add_wrapping(&SYMBOLS_CONSED, 1);
TaggedValue::from_kw_id(intern(&canonical_keyword_name(s.as_ref())))
}
pub fn keyword_id(id: SymId) -> Self {
TaggedValue::from_kw_id(id)
}
pub fn make_keyword(s: impl AsRef<str>) -> Self {
Self::keyword(s)
}
#[inline]
pub fn bool(b: bool) -> Self {
if b { Value::T } else { Value::NIL }
}
pub fn string(s: impl Into<String>) -> Self {
Self::make_string(s)
}
pub fn make_string(s: impl Into<String>) -> Self {
let s = s.into();
let multibyte = !s.is_ascii();
add_wrapping(&STRINGS_CONSED, 1);
add_wrapping(&STRING_CHARS_CONSED, s.len() as u64);
with_tagged_heap(|h| h.alloc_string(LispString::new(s, multibyte)))
}
pub fn heap_string(s: LispString) -> Self {
add_wrapping(&STRINGS_CONSED, 1);
add_wrapping(&STRING_CHARS_CONSED, s.sbytes() as u64);
with_tagged_heap(|h| h.alloc_string(s))
}
pub fn multibyte_string(s: impl Into<String>) -> Self {
let s = s.into();
add_wrapping(&STRINGS_CONSED, 1);
add_wrapping(&STRING_CHARS_CONSED, s.len() as u64);
with_tagged_heap(|h| h.alloc_string(LispString::new(s, true)))
}
pub fn unibyte_string(s: impl Into<String>) -> Self {
let s = s.into();
add_wrapping(&STRINGS_CONSED, 1);
add_wrapping(&STRING_CHARS_CONSED, s.len() as u64);
with_tagged_heap(|h| h.alloc_string(LispString::new(s, false)))
}
pub fn string_with_text_properties(
s: impl Into<String>,
runs: Vec<StringTextPropertyRun>,
) -> Self {
let value = Self::make_string(s);
set_string_text_properties_for_value(value, runs);
value
}
pub fn multibyte_string_with_text_properties(
s: impl Into<String>,
runs: Vec<StringTextPropertyRun>,
) -> Self {
let value = Self::multibyte_string(s);
set_string_text_properties_for_value(value, runs);
value
}
pub fn make_float(f: f64) -> Self {
add_wrapping(&FLOATS_CONSED, 1);
with_tagged_heap(|h| h.alloc_float(f))
}
pub fn bignum(value: rug::Integer) -> Self {
with_tagged_heap(|h| h.alloc_bignum(value))
}
#[inline]
pub fn make_int(value: i64) -> Self {
if (Self::MOST_NEGATIVE_FIXNUM..=Self::MOST_POSITIVE_FIXNUM).contains(&value) {
Self::fixnum(value)
} else {
Self::bignum(rug::Integer::from(value))
}
}
pub fn make_integer(value: rug::Integer) -> Self {
if let Some(small) = value.to_i64() {
if (TaggedValue::MOST_NEGATIVE_FIXNUM..=TaggedValue::MOST_POSITIVE_FIXNUM)
.contains(&small)
{
return Self::fixnum(small);
}
}
Self::bignum(value)
}
pub fn make_integer_from_str_or_zero(text: &str) -> Self {
match rug::Integer::parse(text) {
Ok(incomplete) => Self::make_integer(rug::Integer::from(incomplete)),
Err(_) => Self::fixnum(0),
}
}
pub fn cons(car: Value, cdr: Value) -> Self {
Self::make_cons(car, cdr)
}
pub fn make_cons(car: Value, cdr: Value) -> Self {
if car.is_string() {
let ptr = car.as_string_ptr().unwrap();
let hdr = unsafe { &(*(ptr as *const crate::tagged::header::StringObj)).header };
if !matches!(hdr.kind, crate::tagged::header::HeapObjectKind::String) {
let vlh = unsafe { &*(ptr as *const crate::tagged::header::VecLikeHeader) };
let expected_tagged = ptr as usize | 0b101; panic!(
"CONS CAR BUG: car={:#x} (ptr {:?}, kind={:?}) is corrupt string.\n\
VecLikeHeader.type_tag={:?}\n\
If this were tagged as VecLike it would be {:#x}\n\
car XOR veclike_tagged = {:#x}",
car.0,
ptr,
hdr.kind,
vlh.type_tag,
expected_tagged,
car.0 ^ expected_tagged,
);
}
}
add_wrapping(&CONS_CELLS_CONSED, 1);
with_tagged_heap(|h| h.alloc_cons(car, cdr))
}
pub fn list(mut values: Vec<Value>) -> Self {
let saved_roots = super::eval::save_scratch_gc_roots();
for value in values.iter().copied() {
super::eval::push_scratch_gc_root(value);
}
let mut acc = Value::NIL;
while let Some(item) = values.pop() {
acc = Value::cons(item, acc);
super::eval::push_scratch_gc_root(acc);
}
super::eval::restore_scratch_gc_roots(saved_roots);
acc
}
pub fn list_from_slice(values: &[Value]) -> Self {
let saved_roots = super::eval::save_scratch_gc_roots();
for value in values.iter().copied() {
super::eval::push_scratch_gc_root(value);
}
let mut acc = Value::NIL;
let mut idx = values.len();
while idx > 0 {
idx -= 1;
acc = Value::cons(values[idx], acc);
super::eval::push_scratch_gc_root(acc);
}
super::eval::restore_scratch_gc_roots(saved_roots);
acc
}
pub fn vector(values: Vec<Value>) -> Self {
Self::make_vector(values)
}
pub fn make_vector(values: Vec<Value>) -> Self {
add_wrapping(&VECTOR_CELLS_CONSED, values.len() as u64);
with_tagged_heap(|h| h.alloc_vector(values))
}
pub fn make_record(values: Vec<Value>) -> Self {
add_wrapping(&VECTOR_CELLS_CONSED, values.len() as u64);
with_tagged_heap(|h| h.alloc_record(values))
}
pub fn make_lambda(data: LambdaData) -> Self {
with_tagged_heap(|h| h.alloc_lambda_from_data(data))
}
pub fn make_lambda_with_slots(slots: Vec<Value>) -> Self {
with_tagged_heap(|h| h.alloc_lambda(slots))
}
pub fn make_macro(data: LambdaData) -> Self {
with_tagged_heap(|h| h.alloc_macro_from_data(data))
}
pub fn make_macro_with_slots(slots: Vec<Value>) -> Self {
with_tagged_heap(|h| h.alloc_macro(slots))
}
pub fn make_bytecode(bc: super::bytecode::ByteCodeFunction) -> Self {
with_tagged_heap(|h| h.alloc_bytecode(bc))
}
pub fn hash_table(test: HashTableTest) -> Self {
add_wrapping(&VECTOR_CELLS_CONSED, 1);
with_tagged_heap(|h| h.alloc_hash_table(LispHashTable::new(test)))
}
pub fn hash_table_with_options(
test: HashTableTest,
size: i64,
weakness: Option<HashTableWeakness>,
rehash_size: f64,
rehash_threshold: f64,
) -> Self {
add_wrapping(&VECTOR_CELLS_CONSED, 1);
with_tagged_heap(|h| {
h.alloc_hash_table(LispHashTable::new_with_options(
test,
size,
weakness,
rehash_size,
rehash_threshold,
))
})
}
pub fn make_marker(data: crate::heap_types::MarkerData) -> Self {
with_tagged_heap(|h| h.alloc_marker(data))
}
pub fn make_overlay(data: crate::heap_types::OverlayData) -> Self {
with_tagged_heap(|h| h.alloc_overlay(data))
}
pub fn make_buffer(id: crate::buffer::BufferId) -> Self {
with_tagged_heap(|h| {
if let Some(value) = h.buffer_value(id) {
value
} else {
let value = h.alloc_buffer(id);
h.register_buffer_value(id, value);
value
}
})
}
pub fn make_window(id: u64) -> Self {
with_tagged_heap(|h| {
if let Some(value) = h.window_value(id) {
value
} else {
let value = h.alloc_window(id);
h.register_window_value(id, value);
value
}
})
}
pub fn make_frame(id: u64) -> Self {
with_tagged_heap(|h| {
if let Some(value) = h.frame_value(id) {
value
} else {
let value = h.alloc_frame(id);
h.register_frame_value(id, value);
value
}
})
}
pub fn make_timer(id: u64) -> Self {
with_tagged_heap(|h| {
if let Some(value) = h.timer_value(id) {
value
} else {
let value = h.alloc_timer(id);
h.register_timer_value(id, value);
value
}
})
}
#[inline]
pub fn is_lambda(self) -> bool {
self.veclike_type() == Some(VecLikeType::Lambda)
}
#[inline]
pub fn is_macro(self) -> bool {
self.veclike_type() == Some(VecLikeType::Macro)
}
#[inline]
pub fn is_bytecode(self) -> bool {
self.veclike_type() == Some(VecLikeType::ByteCode)
}
#[inline]
pub fn is_buffer(self) -> bool {
self.veclike_type() == Some(VecLikeType::Buffer)
}
#[inline]
pub fn is_window(self) -> bool {
self.veclike_type() == Some(VecLikeType::Window)
}
#[inline]
pub fn is_frame(self) -> bool {
self.veclike_type() == Some(VecLikeType::Frame)
}
#[inline]
pub fn is_timer(self) -> bool {
self.veclike_type() == Some(VecLikeType::Timer)
}
#[inline]
pub fn is_marker(self) -> bool {
self.veclike_type() == Some(VecLikeType::Marker)
}
#[inline]
pub fn is_overlay(self) -> bool {
self.veclike_type() == Some(VecLikeType::Overlay)
}
pub fn as_str_owned(self) -> Option<String> {
self.as_utf8_str().map(|s| s.to_owned())
}
pub fn as_runtime_string_owned(self) -> Option<String> {
self.as_lisp_string().map(|string| {
crate::emacs_core::string_escape::emacs_bytes_to_storage_string(
string.as_bytes(),
string.is_multibyte(),
)
})
}
pub fn with_str<R>(self, f: impl FnOnce(&str) -> R) -> Option<R> {
self.as_utf8_str().map(f)
}
pub fn as_lisp_string(self) -> Option<&'static LispString> {
self.as_string_ptr().map(|p| unsafe { &(*p).data })
}
pub fn string_is_multibyte(self) -> bool {
self.as_lisp_string().map_or(false, |s| s.is_multibyte())
}
pub fn closure_slots(self) -> Option<&'static LispValueSlice> {
match self.veclike_type()? {
VecLikeType::Lambda => {
let ptr = self.as_veclike_ptr().unwrap() as *const LambdaObj;
Some(unsafe { LispValueSlice::from_slice((*ptr).data.as_slice()) })
}
VecLikeType::Macro => {
let ptr = self.as_veclike_ptr().unwrap() as *const MacroObj;
Some(unsafe { LispValueSlice::from_slice((*ptr).data.as_slice()) })
}
_ => None,
}
}
pub fn with_closure_slots_mut<R>(self, f: impl FnOnce(&mut Vec<Value>) -> R) -> Option<R> {
mutate::with_closure_slots_mut(self, f)
}
pub fn replace_closure_slots(self, slots: Vec<Value>) -> bool {
mutate::replace_closure_slots(self, slots)
}
pub fn set_closure_slot(self, index: usize, value: Value) -> bool {
mutate::set_closure_slot(self, index, value)
}
fn closure_parsed_params_cell(self) -> Option<&'static OnceLock<LambdaParams>> {
match self.veclike_type()? {
VecLikeType::Lambda => {
let ptr = self.as_veclike_ptr().unwrap() as *const LambdaObj;
Some(unsafe { &(*ptr).parsed_params })
}
VecLikeType::Macro => {
let ptr = self.as_veclike_ptr().unwrap() as *const MacroObj;
Some(unsafe { &(*ptr).parsed_params })
}
_ => None,
}
}
pub fn closure_slot(self, index: usize) -> Option<Value> {
self.closure_slots()
.and_then(|slots| slots.get(index).copied())
}
pub fn closure_params(self) -> Option<&'static LambdaParams> {
let cell = self.closure_parsed_params_cell()?;
Some(cell.get_or_init(|| {
let arglist = self.closure_slot(CLOSURE_ARGLIST).unwrap_or(Value::NIL);
crate::emacs_core::builtins::parse_lambda_params_from_value(&arglist)
.unwrap_or_else(|_| LambdaParams::simple(vec![]))
}))
}
pub fn closure_body_value(self) -> Option<Value> {
self.closure_slot(CLOSURE_CODE)
}
pub fn closure_env(self) -> Option<Option<Value>> {
self.closure_slot(CLOSURE_CONSTANTS)
.map(|env| (!env.is_nil()).then_some(env))
}
pub fn closure_doc_value(self) -> Option<Value> {
self.closure_slot(CLOSURE_DOC_STRING)
}
pub fn closure_doc_form(self) -> Option<Option<Value>> {
self.closure_doc_value().map(|doc| {
if doc.is_nil() || doc.is_string() {
None
} else {
Some(doc)
}
})
}
pub fn closure_docstring(self) -> Option<Option<&'static LispString>> {
self.closure_doc_value().map(|doc| {
if doc.is_string() {
doc.as_lisp_string()
} else {
None
}
})
}
pub fn closure_interactive(self) -> Option<Option<Value>> {
self.closure_slot(CLOSURE_INTERACTIVE)
.map(|interactive| (!interactive.is_nil()).then_some(interactive))
}
pub fn get_bytecode_data(self) -> Option<&'static super::bytecode::ByteCodeFunction> {
if self.veclike_type()? == VecLikeType::ByteCode {
let ptr = self.as_veclike_ptr().unwrap() as *const ByteCodeObj;
Some(unsafe { &(*ptr).data })
} else {
None
}
}
pub fn str_ptr_key(self) -> Option<usize> {
self.as_string_ptr().map(|p| p as usize)
}
pub fn as_buffer_id(self) -> Option<crate::buffer::BufferId> {
if self.is_buffer() {
let ptr = self.as_veclike_ptr().unwrap() as *const BufferObj;
Some(unsafe { (*ptr).id })
} else {
None
}
}
pub fn as_window_id(self) -> Option<u64> {
if self.is_window() {
let ptr = self.as_veclike_ptr().unwrap() as *const WindowObj;
Some(unsafe { (*ptr).id })
} else {
None
}
}
pub fn as_frame_id(self) -> Option<u64> {
if self.is_frame() {
let ptr = self.as_veclike_ptr().unwrap() as *const FrameObj;
Some(unsafe { (*ptr).id })
} else {
None
}
}
pub fn as_timer_id(self) -> Option<u64> {
if self.is_timer() {
let ptr = self.as_veclike_ptr().unwrap() as *const TimerObj;
Some(unsafe { (*ptr).id })
} else {
None
}
}
pub fn as_marker_data(self) -> Option<&'static crate::heap_types::MarkerData> {
if self.is_marker() {
let ptr = self.as_veclike_ptr().unwrap() as *const MarkerObj;
Some(unsafe { &(*ptr).data })
} else {
None
}
}
pub fn with_marker_data_mut<R>(
self,
f: impl FnOnce(&mut crate::heap_types::MarkerData) -> R,
) -> Option<R> {
mutate::with_marker_data_mut(self, f)
}
pub fn as_overlay_data(self) -> Option<&'static crate::heap_types::OverlayData> {
if self.is_overlay() {
let ptr = self.as_veclike_ptr().unwrap() as *const OverlayObj;
Some(unsafe { &(*ptr).data })
} else {
None
}
}
pub fn with_overlay_data_mut<R>(
self,
f: impl FnOnce(&mut crate::heap_types::OverlayData) -> R,
) -> Option<R> {
mutate::with_overlay_data_mut(self, f)
}
pub fn as_vector_data(self) -> Option<&'static LispValueSlice> {
if self.is_vector() {
let ptr = self.as_veclike_ptr().unwrap() as *const VectorObj;
Some(unsafe { LispValueSlice::from_slice((*ptr).data.as_slice()) })
} else {
None
}
}
pub fn with_vector_data_mut<R>(self, f: impl FnOnce(&mut Vec<Value>) -> R) -> Option<R> {
mutate::with_vector_data_mut(self, f)
}
pub fn replace_vector_data(self, values: Vec<Value>) -> bool {
mutate::replace_vector_data(self, values)
}
pub fn set_vector_slot(self, index: usize, value: Value) -> bool {
mutate::set_vector_slot(self, index, value)
}
pub fn as_record_data(self) -> Option<&'static LispValueSlice> {
if self.is_record() {
let ptr = self.as_veclike_ptr().unwrap() as *const RecordObj;
Some(unsafe { LispValueSlice::from_slice((*ptr).data.as_slice()) })
} else {
None
}
}
pub fn with_record_data_mut<R>(self, f: impl FnOnce(&mut Vec<Value>) -> R) -> Option<R> {
mutate::with_record_data_mut(self, f)
}
pub fn replace_record_data(self, values: Vec<Value>) -> bool {
mutate::replace_record_data(self, values)
}
pub fn set_record_slot(self, index: usize, value: Value) -> bool {
mutate::set_record_slot(self, index, value)
}
pub fn replace_vectorlike_sequence_data(self, values: Vec<Value>) -> bool {
match self.veclike_type() {
Some(VecLikeType::Vector) => self.replace_vector_data(values),
Some(VecLikeType::Record) => self.replace_record_data(values),
_ => false,
}
}
pub fn as_hash_table(self) -> Option<&'static LispHashTable> {
if self.is_hash_table() {
let ptr = self.as_veclike_ptr().unwrap() as *const HashTableObj;
Some(unsafe { &(*ptr).table })
} else {
None
}
}
pub fn with_hash_table_mut<R>(self, f: impl FnOnce(&mut LispHashTable) -> R) -> Option<R> {
mutate::with_hash_table_mut(self, f)
}
pub fn replace_hash_table(self, table: LispHashTable) -> bool {
self.with_hash_table_mut(|current| *current = table)
.is_some()
}
pub fn with_bytecode_data_mut<R>(
self,
f: impl FnOnce(&mut super::bytecode::ByteCodeFunction) -> R,
) -> Option<R> {
mutate::with_bytecode_data_mut(self, f)
}
pub fn with_lisp_string_mut<R>(self, f: impl FnOnce(&mut LispString) -> R) -> Option<R> {
mutate::with_lisp_string_mut(self, f)
}
pub fn to_hash_key(&self, test: &HashTableTest) -> HashKey {
match test {
HashTableTest::Eq => self.to_eq_key(),
HashTableTest::Eql => self.to_eql_key(),
HashTableTest::Equal => self.to_equal_key(),
}
}
pub fn to_hash_key_swp(&self, test: &HashTableTest, symbols_with_pos_enabled: bool) -> HashKey {
match test {
HashTableTest::Eq => self.to_eq_key_swp(symbols_with_pos_enabled),
HashTableTest::Eql => self.to_eql_key_swp(symbols_with_pos_enabled),
HashTableTest::Equal => self.to_equal_key_swp(symbols_with_pos_enabled),
}
}
pub fn to_eq_key_swp(&self, symbols_with_pos_enabled: bool) -> HashKey {
if symbols_with_pos_enabled && self.is_symbol_with_pos() {
let sym = self.as_symbol_with_pos_sym().unwrap();
return sym.to_eq_key();
}
self.to_eq_key()
}
pub fn to_eql_key_swp(&self, symbols_with_pos_enabled: bool) -> HashKey {
if symbols_with_pos_enabled && self.is_symbol_with_pos() {
let sym = self.as_symbol_with_pos_sym().unwrap();
return sym.to_eql_key();
}
self.to_eql_key()
}
fn to_eq_key(&self) -> HashKey {
match self.kind() {
ValueKind::Nil => HashKey::Nil,
ValueKind::T => HashKey::True,
ValueKind::Fixnum(n) => HashKey::Int(n),
ValueKind::Float => {
HashKey::Ptr(self.bits())
}
ValueKind::Symbol(id) => HashKey::Symbol(id),
ValueKind::Subr(_) => HashKey::Ptr(self.bits()),
ValueKind::Cons | ValueKind::String | ValueKind::Veclike(_) => {
HashKey::Ptr(self.bits())
}
ValueKind::Unbound | ValueKind::Unknown => HashKey::Ptr(self.bits()),
}
}
fn to_eql_key(&self) -> HashKey {
match self.kind() {
ValueKind::Fixnum(n) => HashKey::Int(n),
ValueKind::Float => HashKey::Float(self.xfloat().to_bits()),
_ => self.to_eq_key(),
}
}
fn to_equal_key(&self) -> HashKey {
let mut seen = Vec::new();
self.to_equal_key_depth_swp(0, &mut seen, false)
}
fn to_equal_key_swp(&self, symbols_with_pos_enabled: bool) -> HashKey {
let mut seen = Vec::new();
self.to_equal_key_depth_swp(0, &mut seen, symbols_with_pos_enabled)
}
fn to_equal_key_depth(&self, depth: usize, seen: &mut Vec<usize>) -> HashKey {
self.to_equal_key_depth_swp(depth, seen, false)
}
fn to_equal_key_depth_swp(
&self,
depth: usize,
seen: &mut Vec<usize>,
symbols_with_pos_enabled: bool,
) -> HashKey {
if depth > 200 {
return self.to_eq_key();
}
match self.kind() {
ValueKind::Nil => HashKey::Nil,
ValueKind::T => HashKey::True,
ValueKind::Fixnum(n) => HashKey::Int(n),
ValueKind::Float => HashKey::Float(self.xfloat().to_bits()),
ValueKind::Symbol(id) => HashKey::Symbol(id),
ValueKind::Veclike(VecLikeType::SymbolWithPos) => {
let swp = self.as_symbol_with_pos().unwrap();
if symbols_with_pos_enabled {
return swp.sym.to_equal_key_depth_swp(
depth + 1,
seen,
symbols_with_pos_enabled,
);
}
HashKey::SymbolWithPos(
Box::new(swp.sym.to_eq_key()),
Box::new(swp.pos.to_equal_key_depth_swp(
depth + 1,
seen,
symbols_with_pos_enabled,
)),
)
}
ValueKind::String => {
if let Some(s) = self.as_utf8_str() {
HashKey::Text(s.to_string())
} else {
self.to_eq_key()
}
}
ValueKind::Cons => {
let ptr = self.bits();
if let Some(index) = seen.iter().position(|&p| p == ptr) {
return HashKey::Cycle(index as u32);
}
seen.push(ptr);
let car = self.cons_car();
let cdr = self.cons_cdr();
let car_key = car.to_equal_key_depth_swp(depth + 1, seen, symbols_with_pos_enabled);
let cdr_key = cdr.to_equal_key_depth_swp(depth + 1, seen, symbols_with_pos_enabled);
seen.pop();
HashKey::EqualCons(Box::new(car_key), Box::new(cdr_key))
}
ValueKind::Veclike(VecLikeType::Vector) | ValueKind::Veclike(VecLikeType::Record) => {
let ptr = self.bits();
if let Some(index) = seen.iter().position(|&p| p == ptr) {
return HashKey::Cycle(index as u32);
}
seen.push(ptr);
let items = if self.is_vector() {
self.as_vector_data().unwrap().clone()
} else {
self.as_record_data().unwrap().clone()
};
let keys: Vec<HashKey> = items
.iter()
.map(|item| {
item.to_equal_key_depth_swp(depth + 1, seen, symbols_with_pos_enabled)
})
.collect();
seen.pop();
HashKey::EqualVec(keys)
}
ValueKind::Veclike(VecLikeType::Marker) => {
super::marker::marker_equal_hash_key_value(self)
}
ValueKind::Veclike(VecLikeType::Lambda) => {
let ptr = self.bits();
if let Some(index) = seen.iter().position(|&p| p == ptr) {
return HashKey::Cycle(index as u32);
}
seen.push(ptr);
let key = closure_to_equal_key(*self, depth + 1, seen);
seen.pop();
key
}
_ => self.to_eq_key(),
}
}
pub(crate) fn memory_use_counts_snapshot() -> [i64; 7] {
[
as_neovm_int(CONS_CELLS_CONSED.load(Ordering::Relaxed)),
as_neovm_int(FLOATS_CONSED.load(Ordering::Relaxed)),
as_neovm_int(VECTOR_CELLS_CONSED.load(Ordering::Relaxed)),
as_neovm_int(SYMBOLS_CONSED.load(Ordering::Relaxed)),
as_neovm_int(STRING_CHARS_CONSED.load(Ordering::Relaxed)),
as_neovm_int(INTERVALS_CONSED.load(Ordering::Relaxed)),
as_neovm_int(STRINGS_CONSED.load(Ordering::Relaxed)),
]
}
}
pub fn eq_value(left: &Value, right: &Value) -> bool {
left.bits() == right.bits()
}
pub fn eq_value_swp(left: &Value, right: &Value, symbols_with_pos_enabled: bool) -> bool {
if left.bits() == right.bits() {
return true;
}
if !symbols_with_pos_enabled {
return false;
}
let l = if left.is_symbol_with_pos() {
left.as_symbol_with_pos_sym().unwrap()
} else {
*left
};
let r = if right.is_symbol_with_pos() {
right.as_symbol_with_pos_sym().unwrap()
} else {
*right
};
l.bits() == r.bits()
}
pub fn eql_value(left: &Value, right: &Value) -> bool {
if left.bits() == right.bits() {
return true;
}
match (left.kind(), right.kind()) {
(ValueKind::Float, ValueKind::Float) => left.xfloat().to_bits() == right.xfloat().to_bits(),
_ => false,
}
}
pub fn eql_value_swp(left: &Value, right: &Value, symbols_with_pos_enabled: bool) -> bool {
if eq_value_swp(left, right, symbols_with_pos_enabled) {
return true;
}
match (left.kind(), right.kind()) {
(ValueKind::Float, ValueKind::Float) => left.xfloat().to_bits() == right.xfloat().to_bits(),
_ => false,
}
}
pub fn equal_value(left: &Value, right: &Value, depth: usize) -> bool {
let mut seen = None;
equal_value_inner(left, right, depth, &mut seen, false)
}
pub fn equal_value_swp(
left: &Value,
right: &Value,
depth: usize,
symbols_with_pos_enabled: bool,
) -> bool {
let mut seen = None;
equal_value_inner(left, right, depth, &mut seen, symbols_with_pos_enabled)
}
fn equal_value_inner(
left: &Value,
right: &Value,
depth: usize,
seen: &mut Option<HashSet<(usize, usize)>>,
symbols_with_pos_enabled: bool,
) -> bool {
if depth > 200 {
return false;
}
let mut left = *left;
let mut right = *right;
if symbols_with_pos_enabled {
if left.is_symbol_with_pos() {
left = left.as_symbol_with_pos_sym().unwrap();
}
if right.is_symbol_with_pos() {
right = right.as_symbol_with_pos_sym().unwrap();
}
}
if left.bits() == right.bits() {
return true;
}
if left.is_fixnum() || right.is_fixnum() || left.is_symbol() || right.is_symbol() {
return false;
}
if left.is_float() {
return right.is_float() && left.xfloat().to_bits() == right.xfloat().to_bits();
}
if left.is_string() {
return if right.is_string() {
match (left.as_lisp_string(), right.as_lisp_string()) {
(Some(left_string), Some(right_string)) => {
left_string.schars() == right_string.schars()
&& left_string.sbytes() == right_string.sbytes()
&& left_string.as_bytes() == right_string.as_bytes()
}
_ => false,
}
} else {
false
};
}
if left.is_cons() {
if !right.is_cons() {
return false;
}
if depth > 10 {
let pair = (left.bits(), right.bits());
if !seen.get_or_insert_with(HashSet::new).insert(pair) {
return true;
}
}
let a_car = left.cons_car();
let a_cdr = left.cons_cdr();
let b_car = right.cons_car();
let b_cdr = right.cons_cdr();
return equal_value_inner(&a_car, &b_car, depth + 1, seen, symbols_with_pos_enabled)
&& equal_value_inner(&a_cdr, &b_cdr, depth + 1, seen, symbols_with_pos_enabled);
}
if !left.is_veclike() || !right.is_veclike() {
return false;
}
let Some(left_type) = left.veclike_type() else {
return false;
};
let Some(right_type) = right.veclike_type() else {
return false;
};
if left_type != right_type {
return false;
}
match left_type {
VecLikeType::Marker => {
super::marker::marker_logical_fields(&left)
== super::marker::marker_logical_fields(&right)
}
VecLikeType::Vector | VecLikeType::Record => {
if depth > 10 {
let pair = (left.bits(), right.bits());
if !seen.get_or_insert_with(HashSet::new).insert(pair) {
return true;
}
}
let av = left.as_vector_data().or_else(|| left.as_record_data());
let bv = right.as_vector_data().or_else(|| right.as_record_data());
match (av, bv) {
(Some(a), Some(b)) => {
if a.len() != b.len() {
return false;
}
a.iter().zip(b.iter()).all(|(x, y)| {
equal_value_inner(x, y, depth + 1, seen, symbols_with_pos_enabled)
})
}
_ => false,
}
}
VecLikeType::HashTable => false,
VecLikeType::Lambda => {
if depth > 10 {
let pair = (left.bits(), right.bits());
if !seen.get_or_insert_with(HashSet::new).insert(pair) {
return true;
}
}
closure_equal(&left, &right, depth + 1, seen, symbols_with_pos_enabled)
}
VecLikeType::SymbolWithPos => {
if symbols_with_pos_enabled {
unreachable!("symbol-with-pos values are unwrapped before equality dispatch")
} else {
let l = left.as_symbol_with_pos().unwrap();
let r = right.as_symbol_with_pos().unwrap();
l.sym.bits() == r.sym.bits() && l.pos.bits() == r.pos.bits()
}
}
_ => false,
}
}
fn closure_params_to_equal_key(params: &LambdaParams) -> HashKey {
let mut values = Vec::with_capacity(params.required.len() + params.optional.len() + 3);
values.push(HashKey::Text("params".to_string()));
for sym in ¶ms.required {
values.push(HashKey::Symbol(*sym));
}
if !params.optional.is_empty() {
values.push(HashKey::Text("&optional".to_string()));
for sym in ¶ms.optional {
values.push(HashKey::Symbol(*sym));
}
}
if let Some(rest) = params.rest {
values.push(HashKey::Text("&rest".to_string()));
values.push(HashKey::Symbol(rest));
}
HashKey::EqualVec(values)
}
fn closure_to_equal_key(value: Value, depth: usize, seen: &mut Vec<usize>) -> HashKey {
if depth > 200 {
return HashKey::Text("#<lambda-depth-limit>".to_string());
}
let Some(params) = value.closure_params() else {
return HashKey::Text("#<invalid-lambda>".to_string());
};
let mut slots = vec![
HashKey::Text("lambda".to_string()),
closure_params_to_equal_key(params),
value
.closure_body_value()
.map_or(HashKey::Nil, |body| body.to_equal_key_depth(0, seen)),
match value.closure_env().unwrap_or(None) {
Some(env) => env.to_equal_key_depth(0, seen),
None => HashKey::Text("dynamic".to_string()),
},
];
if let Some(doc_value) = value.closure_doc_value()
&& !doc_value.is_nil()
{
slots.push(HashKey::Nil);
let doc = if doc_value.is_string() {
let string = doc_value.as_lisp_string().expect("string");
HashKey::Text(
crate::emacs_core::string_escape::emacs_bytes_to_storage_string(
string.as_bytes(),
string.is_multibyte(),
),
)
} else {
doc_value.to_equal_key_depth(0, seen)
};
slots.push(doc);
}
HashKey::EqualVec(slots)
}
fn closure_equal(
left: &Value,
right: &Value,
depth: usize,
seen: &mut Option<HashSet<(usize, usize)>>,
symbols_with_pos_enabled: bool,
) -> bool {
let (Some(left_params), Some(right_params)) = (left.closure_params(), right.closure_params())
else {
return false;
};
if left_params != right_params {
return false;
}
let body_equal = match (left.closure_body_value(), right.closure_body_value()) {
(Some(left_body), Some(right_body)) => equal_value_inner(
&left_body,
&right_body,
depth + 1,
seen,
symbols_with_pos_enabled,
),
(None, None) => true,
_ => false,
};
if !body_equal {
return false;
}
let env_equal = match (
left.closure_env().unwrap_or(None),
right.closure_env().unwrap_or(None),
) {
(None, None) => true,
(Some(l), Some(r)) => equal_value_inner(&l, &r, depth + 1, seen, symbols_with_pos_enabled),
_ => false,
};
if !env_equal || left.closure_docstring().flatten() != right.closure_docstring().flatten() {
return false;
}
match (
left.closure_doc_form().flatten(),
right.closure_doc_form().flatten(),
) {
(None, None) => true,
(Some(l), Some(r)) => equal_value_inner(&l, &r, depth + 1, seen, symbols_with_pos_enabled),
_ => false,
}
}
pub fn list_to_vec(value: &Value) -> Option<Vec<Value>> {
let mut result = Vec::new();
let mut tortoise = *value;
let mut hare = *value;
let mut step = 0u64;
loop {
if hare.is_nil() {
return Some(result);
} else if hare.is_cons() {
result.push(hare.cons_car());
hare = hare.cons_cdr();
step += 1;
if step % 2 == 0 {
if tortoise.is_cons() {
tortoise = tortoise.cons_cdr();
}
if tortoise.bits() == hare.bits() {
return None; }
}
} else {
return None;
}
}
}
pub fn list_length(value: &Value) -> Option<usize> {
let mut len = 0;
let mut tortoise = *value;
let mut hare = *value;
loop {
if hare.is_nil() {
return Some(len);
} else if hare.is_cons() {
len += 1;
hare = hare.cons_cdr();
if hare.is_nil() {
return Some(len);
} else if hare.is_cons() {
len += 1;
hare = hare.cons_cdr();
} else {
return None; }
if tortoise.is_cons() {
tortoise = tortoise.cons_cdr();
}
if tortoise.bits() == hare.bits() {
return None; }
} else {
return None;
}
}
}
impl fmt::Display for TaggedValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", super::print::print_value(self))
}
}
pub fn lexenv_assq(lexenv: Value, sym_id: SymId) -> Option<Value> {
let mut cursor = lexenv;
loop {
if cursor.is_cons() {
let car = cursor.cons_car();
if car.is_cons() {
let binding_sym = car.cons_car();
if let Some(s) = lexenv_binding_symbol_id(binding_sym) {
if s == sym_id {
return Some(car);
}
}
}
cursor = cursor.cons_cdr();
} else {
return None;
}
}
}
fn lexenv_binding_symbol_id(value: Value) -> Option<SymId> {
match value.kind() {
ValueKind::Symbol(sym) => Some(sym),
ValueKind::T => Some(SymId(1)),
ValueKind::Nil => Some(SymId(0)),
_ => None,
}
}
pub(crate) fn lexenv_binding_symbol_value(sym_id: SymId) -> Value {
TaggedValue::from_sym_id(sym_id)
}
pub fn lexenv_lookup(lexenv: Value, sym_id: SymId) -> Option<Value> {
lexenv_assq(lexenv, sym_id).map(|cell| cell.cons_cdr())
}
pub fn lexenv_declares_special(lexenv: Value, sym_id: SymId) -> bool {
let mut cursor = lexenv;
loop {
if cursor.is_cons() {
let car = cursor.cons_car();
if let Some(id) = car.as_symbol_id() {
if id == sym_id {
return true;
}
}
cursor = cursor.cons_cdr();
} else {
return false;
}
}
}
pub fn lexenv_set(cell: Value, value: Value) {
cell.set_cdr(value);
}
pub fn lexenv_prepend(lexenv: Value, sym_id: SymId, val: Value) -> Value {
let binding = Value::make_cons(lexenv_binding_symbol_value(sym_id), val);
Value::make_cons(binding, lexenv)
}
#[cfg(test)]
#[macro_export]
macro_rules! assert_val_eq {
($left:expr, $right:expr) => {{
let left_val = &$left;
let right_val = &$right;
if !$crate::emacs_core::value::equal_value(left_val, right_val, 0) {
panic!(
"assertion `left == right` failed (structural)\n left: {}\n right: {}",
$crate::emacs_core::print::print_value(left_val),
$crate::emacs_core::print::print_value(right_val),
);
}
}};
($left:expr, $right:expr, $($msg:tt)+) => {{
let left_val = &$left;
let right_val = &$right;
if !$crate::emacs_core::value::equal_value(left_val, right_val, 0) {
panic!(
"assertion `left == right` failed (structural): {}\n left: {}\n right: {}",
format_args!($($msg)+),
$crate::emacs_core::print::print_value(left_val),
$crate::emacs_core::print::print_value(right_val),
);
}
}};
}
#[cfg(test)]
#[path = "value_test.rs"]
mod tests;