use std::cell::RefCell;
use std::collections::{HashMap, HashSet, VecDeque};
use std::hash::{Hash, Hasher};
use std::rc::Rc;
use std::sync::OnceLock;
use smallvec::SmallVec;
use super::abbrev::AbbrevManager;
use super::advice::VariableWatcherList;
use super::autoload::AutoloadManager;
use super::bookmark::BookmarkManager;
use super::builtins;
use super::coding::CodingSystemManager;
use super::custom::CustomManager;
use super::doc::{STARTUP_VARIABLE_DOC_STRING_PROPERTIES, STARTUP_VARIABLE_DOC_STUBS};
use super::error::*;
use super::interactive::InteractiveRegistry;
use super::intern::{
NameId, SymId, intern, intern_uninterned, is_canonical_id, is_keyword_id, resolve_name,
resolve_sym, resolve_sym_metadata, symbol_name_id,
};
use super::keymap::{
list_keymap_define, list_keymap_set_parent, make_list_keymap, make_sparse_list_keymap,
};
use super::kmacro::KmacroManager;
use super::minibuffer::MinibufferManager;
use super::mode::ModeRegistry;
use super::process::ProcessManager;
use super::rect::RectangleState;
use super::regex::MatchData;
use super::register::RegisterManager;
use super::symbol::Obarray;
use super::threads::ThreadManager;
use super::timer::TimerManager;
use super::value::*;
use crate::buffer::BufferManager;
use crate::face::{Face as RuntimeFace, FaceTable, FontSlant, FontWeight, FontWidth};
use crate::gc_trace::GcTrace;
use crate::tagged::header::{CLOSURE_ARGLIST, SubrDispatchKind, SubrFn, SubrObj};
use crate::window::FrameManager;
const EVAL_STACK_RED_ZONE: usize = 128 * 1024;
const EVAL_STACK_SEGMENT: usize = 2 * 1024 * 1024;
const STACK_GROWTH_PROBE_START_DEPTH: usize = 16;
const STACK_GROWTH_PROBE_INTERVAL: usize = 16;
const NAMED_CALL_CACHE_CAPACITY: usize = 4096;
const LEXENV_ASSQ_CACHE_CAPACITY: usize = 16;
const LEXENV_SPECIAL_CACHE_CAPACITY: usize = 16;
const GC_DEFAULT_THRESHOLD_BYTES: usize = 1_000_000 * std::mem::size_of::<usize>();
const GC_THRESHOLD_FLOOR_BYTES: usize = GC_DEFAULT_THRESHOLD_BYTES / 10;
const GC_HI_THRESHOLD_BYTES: usize = (i64::MAX as usize) / 2;
const GC_PERCENT_SCALE: u64 = 1_000_000;
pub(crate) const INTERNAL_COMPILER_FUNCTION_OVERRIDES: &str =
"internal--compiler-function-overrides";
#[derive(Clone, Debug, PartialEq, Eq)]
struct RedisplaySignature {
selected_frame: Option<u64>,
selected_window: Option<u64>,
current_buffer: Option<u64>,
current_message: Option<crate::heap_types::LispString>,
active_minibuffer_window: Option<u64>,
minibuffer_selected_window: Option<u64>,
face_change_count: u64,
obarray_function_epoch: u64,
redisplay_generation: u64,
frame: Option<RedisplayFrameSignature>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct RedisplayFrameSignature {
id: u64,
width: u32,
height: u32,
char_width: u32,
char_height: u32,
font_pixel_size: u32,
visible: bool,
selected_window: u64,
window_state_change: bool,
windows: Vec<RedisplayWindowSignature>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct RedisplayWindowSignature {
id: u64,
buffer_id: u64,
bounds: (u32, u32, u32, u32),
window_start: usize,
window_end_pos: usize,
window_end_bytepos: usize,
window_end_vpos: usize,
window_end_valid: bool,
point: usize,
old_point: usize,
hscroll: usize,
vscroll: i32,
preserve_vscroll_p: bool,
buffer: Option<RedisplayBufferSignature>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct RedisplayBufferSignature {
id: u64,
modified_tick: i64,
chars_modified_tick: i64,
save_modified_tick: i64,
autosave_modified_tick: i64,
point: usize,
point_byte: usize,
begv: usize,
begv_byte: usize,
zv: usize,
zv_byte: usize,
total_chars: usize,
total_bytes: usize,
last_window_start: usize,
last_selected_window: Option<u64>,
}
fn redisplay_f32_bits(value: f32) -> u32 {
if value == 0.0 {
0.0f32.to_bits()
} else {
value.to_bits()
}
}
#[derive(Clone, Copy)]
pub(crate) struct SubrEntry {
pub(crate) function: Option<crate::tagged::header::SubrFn>,
pub(crate) min_args: u16,
pub(crate) max_args: Option<u16>,
pub(crate) dispatch_kind: crate::tagged::header::SubrDispatchKind,
pub(crate) name_id: crate::emacs_core::intern::NameId,
}
thread_local! {
static GLOBAL_SUBR_TABLE: RefCell<Vec<Option<SubrEntry>>> = const { RefCell::new(Vec::new()) };
static QUIT_REQUESTED_TLS: RefCell<Option<std::sync::Arc<std::sync::atomic::AtomicBool>>> = const { RefCell::new(None) };
}
pub(crate) fn tls_quit_pending() -> bool {
QUIT_REQUESTED_TLS.with(|cell| {
cell.borrow()
.as_ref()
.is_some_and(|flag| flag.load(std::sync::atomic::Ordering::Relaxed))
})
}
pub(crate) fn register_global_subr_entry(sym_id: SymId, entry: SubrEntry) {
GLOBAL_SUBR_TABLE.with(|table| {
let idx = sym_id.0 as usize;
let mut table = table.borrow_mut();
if table.len() <= idx {
table.resize_with(idx + 1, || None);
}
table[idx] = Some(entry);
});
crate::tagged::value::update_static_subr_object_entry(
sym_id,
entry.function,
entry.min_args,
entry.max_args,
entry.dispatch_kind,
);
}
pub(crate) fn lookup_global_subr_entry(sym_id: SymId) -> Option<SubrEntry> {
GLOBAL_SUBR_TABLE.with(|table| table.borrow().get(sym_id.0 as usize).copied().flatten())
}
#[inline(always)]
fn subr_entry_from_value(function: Value) -> Option<(SymId, SubrEntry)> {
let ptr = function.as_veclike_ptr()?;
let header = unsafe { &*ptr };
if header.type_tag != VecLikeType::Subr {
return None;
}
let subr = unsafe { &*(ptr as *const SubrObj) };
if subr.function.is_none() && subr.dispatch_kind == SubrDispatchKind::Builtin {
return None;
}
Some((
subr.sym_id,
SubrEntry {
function: subr.function,
min_args: subr.min_args,
max_args: subr.max_args,
dispatch_kind: subr.dispatch_kind,
name_id: subr.name,
},
))
}
pub(crate) fn with_global_subr_entry<R>(
sym_id: SymId,
f: impl FnOnce(&SubrEntry) -> R,
) -> Option<R> {
GLOBAL_SUBR_TABLE.with(|table| {
table
.borrow()
.get(sym_id.0 as usize)
.and_then(|entry| entry.as_ref().map(f))
})
}
pub(crate) fn clear_global_subr_table() {
GLOBAL_SUBR_TABLE.with(|table| table.borrow_mut().clear());
}
fn internal_compiler_function_overrides_sym() -> SymId {
static SYM: OnceLock<SymId> = OnceLock::new();
*SYM.get_or_init(|| intern(INTERNAL_COMPILER_FUNCTION_OVERRIDES))
}
#[inline]
fn internal_make_interpreted_closure_function_symbol() -> SymId {
static SYM: OnceLock<SymId> = OnceLock::new();
*SYM.get_or_init(|| intern("internal-make-interpreted-closure-function"))
}
#[inline]
fn cconv_make_interpreted_closure_symbol() -> SymId {
static SYM: OnceLock<SymId> = OnceLock::new();
*SYM.get_or_init(|| intern("cconv-make-interpreted-closure"))
}
#[inline]
fn load_in_progress_symbol() -> SymId {
static SYM: OnceLock<SymId> = OnceLock::new();
*SYM.get_or_init(|| intern("load-in-progress"))
}
#[inline]
fn macroexpand_all_environment_symbol() -> SymId {
static SYM: OnceLock<SymId> = OnceLock::new();
*SYM.get_or_init(|| intern("macroexpand-all-environment"))
}
#[inline]
fn throw_symbol() -> SymId {
static SYM: OnceLock<SymId> = OnceLock::new();
*SYM.get_or_init(|| intern("throw"))
}
pub(crate) fn compiler_function_override_in_obarray(
obarray: &Obarray,
sym_id: SymId,
) -> Option<Value> {
let overrides_sym = internal_compiler_function_overrides_sym();
let mut cursor = obarray.symbol_value_id_or_nil(overrides_sym);
while cursor.is_cons() {
let entry = cursor.cons_car();
cursor = cursor.cons_cdr();
if entry.is_cons() && entry.cons_car().as_symbol_id() == Some(sym_id) {
return Some(entry.cons_cdr());
}
}
None
}
pub(crate) fn compiler_function_overrides_active_in_obarray(obarray: &Obarray) -> bool {
let overrides_sym = internal_compiler_function_overrides_sym();
obarray.symbol_value_id_or_nil(overrides_sym).is_cons()
}
#[derive(Clone, Debug)]
struct ExecutingKbdMacroRuntimeScope {
snapshot: crate::keyboard::ExecutingKbdMacroRuntimeSnapshot,
real_this_command: Value,
}
#[derive(Clone, Debug)]
pub(crate) enum SpecBinding {
Let {
sym_id: SymId,
old_value: Option<Value>,
},
LetLocal {
sym_id: SymId,
old_value: Value,
buffer_id: crate::buffer::BufferId,
},
LetDefault {
sym_id: SymId,
old_value: Option<Value>,
},
LexicalEnv { old_lexenv: Value },
GcRoot { value: Value },
Backtrace {
function: Value,
args: BacktraceArgs,
debug_on_exit: bool,
},
UnwindProtect { forms: Value, lexenv: Value },
SaveExcursion {
buffer_id: crate::buffer::BufferId,
marker_id: u64,
marker: Value,
},
SaveCurrentBuffer { buffer_id: crate::buffer::BufferId },
SaveRestriction {
state: crate::buffer::SavedRestrictionState,
},
Nop,
}
#[derive(Clone, Debug)]
pub(crate) enum BacktraceArgs {
Unevalled(Value),
Evaluated(LispArgVec),
}
impl BacktraceArgs {
#[inline]
pub(crate) fn is_unevalled(&self) -> bool {
matches!(self, Self::Unevalled(_))
}
#[inline]
pub(crate) fn as_slice(&self) -> &[Value] {
match self {
Self::Unevalled(value) => std::slice::from_ref(value),
Self::Evaluated(args) => args.as_slice(),
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct VmRootFrame {
pub(crate) roots: LispArgVec,
}
impl VmRootFrame {
fn new() -> Self {
Self {
roots: LispArgVec::new(),
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct PendingSafeFuncall {
pub(crate) function: Value,
pub(crate) args: LispArgVec,
}
pub(crate) type LispArgVec = SmallVec<[Value; 8]>;
type LetBindingVec = SmallVec<[(SymId, Value); 8]>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct GnuTimerTimestamp {
pub(crate) high_seconds: i64,
pub(crate) low_seconds: i64,
pub(crate) usecs: i64,
pub(crate) psecs: i64,
}
impl GnuTimerTimestamp {
pub(crate) fn now() -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
let (secs, usecs) = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(dur) => (dur.as_secs() as i64, dur.subsec_micros() as i64),
Err(err) => {
let dur = err.duration();
(-(dur.as_secs() as i64), -(dur.subsec_micros() as i64))
}
};
Self {
high_seconds: secs >> 16,
low_seconds: secs & 0xFFFF,
usecs,
psecs: 0,
}
}
fn unix_seconds(self) -> i64 {
(self.high_seconds << 16) + self.low_seconds
}
pub(crate) fn duration_until(self, now: Self) -> std::time::Duration {
use std::time::Duration;
if self <= now {
return Duration::ZERO;
}
let mut secs = self.unix_seconds() - now.unix_seconds();
let mut usecs = self.usecs - now.usecs;
let mut psecs = self.psecs - now.psecs;
if psecs < 0 {
psecs += 1_000_000;
usecs -= 1;
}
if usecs < 0 {
usecs += 1_000_000;
secs -= 1;
}
if secs < 0 {
return Duration::ZERO;
}
let mut secs = secs as u64;
let mut nanos = (usecs as u32) * 1_000 + ((psecs.max(0) as u32) + 999) / 1_000;
if nanos >= 1_000_000_000 {
secs += 1;
nanos -= 1_000_000_000;
}
Duration::new(secs, nanos)
}
pub(crate) fn overdue_duration(self, now: Self) -> std::time::Duration {
use std::time::Duration;
if self >= now {
return Duration::ZERO;
}
let mut secs = now.unix_seconds() - self.unix_seconds();
let mut usecs = now.usecs - self.usecs;
let mut psecs = now.psecs - self.psecs;
if psecs < 0 {
psecs += 1_000_000;
usecs -= 1;
}
if usecs < 0 {
usecs += 1_000_000;
secs -= 1;
}
let nanos = ((usecs as u32) * 1_000) + (psecs as u32 / 1_000);
Duration::new(secs as u64, nanos)
}
pub(crate) fn from_duration(duration: std::time::Duration) -> Self {
let secs = duration.as_secs() as i64;
let usecs = duration.subsec_micros() as i64;
Self {
high_seconds: secs >> 16,
low_seconds: secs & 0xFFFF,
usecs,
psecs: 0,
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct PendingGnuTimer {
pub(crate) timer: Value,
pub(crate) when: GnuTimerTimestamp,
}
fn runtime_tail_fingerprint(tail: &[Value]) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
let mut seen = std::collections::HashSet::new();
tail.len().hash(&mut hasher);
for (i, value) in tail.iter().enumerate() {
i.hash(&mut hasher);
value_fingerprint(*value, &mut hasher, 3, &mut seen);
}
hasher.finish()
}
fn value_fingerprint(
value: Value,
hasher: &mut impl std::hash::Hasher,
depth: usize,
seen: &mut std::collections::HashSet<usize>,
) {
use std::hash::Hash;
match value.kind() {
ValueKind::Nil => 0u8.hash(hasher),
ValueKind::T => 1u8.hash(hasher),
ValueKind::Fixnum(n) => {
2u8.hash(hasher);
n.hash(hasher);
}
ValueKind::Float => {
3u8.hash(hasher);
value.as_float().unwrap().to_bits().hash(hasher);
}
ValueKind::Symbol(id) => {
4u8.hash(hasher);
id.0.hash(hasher);
}
ValueKind::String => {
5u8.hash(hasher);
let key = value.bits() as usize;
if depth == 0 || !seen.insert(key) {
key.hash(hasher);
return;
}
let string = match value.as_lisp_string() {
Some(string) => string,
None => {
key.hash(hasher);
return;
}
};
string.is_multibyte().hash(hasher);
string.schars().hash(hasher);
string.sbytes().hash(hasher);
for byte in string.as_bytes().iter().take(32) {
byte.hash(hasher);
}
}
ValueKind::Cons => {
6u8.hash(hasher);
let key = value.bits() as usize;
if depth == 0 || !seen.insert(key) {
key.hash(hasher);
return;
}
let mut cursor = value;
let mut count = 0usize;
while count < 4 && cursor.is_cons() {
count.hash(hasher);
value_fingerprint(cursor.cons_car(), hasher, depth - 1, seen);
cursor = cursor.cons_cdr();
count += 1;
}
value_fingerprint(cursor, hasher, depth - 1, seen);
}
ValueKind::Veclike(VecLikeType::Vector) => {
7u8.hash(hasher);
let key = value.bits() as usize;
if depth == 0 || !seen.insert(key) {
key.hash(hasher);
return;
}
let items = value.as_vector_data().unwrap();
items.len().hash(hasher);
for item in items.iter().take(4) {
value_fingerprint(*item, hasher, depth - 1, seen);
}
}
ValueKind::Subr(sym_id) => {
8u8.hash(hasher);
sym_id.0.hash(hasher);
}
ValueKind::Veclike(VecLikeType::Subr) => {
8u8.hash(hasher);
value.as_subr_id().unwrap().0.hash(hasher);
}
_ => {
9u8.hash(hasher);
value.bits().hash(hasher);
}
}
}
fn interpreted_closure_env_entries(lexenv: Value) -> Vec<InterpretedClosureEnvEntry> {
let mut cursor = lexenv;
let mut entries = Vec::new();
loop {
match cursor.kind() {
ValueKind::Cons => {
let pair_car = cursor.cons_car();
let pair_cdr = cursor.cons_cdr();
match pair_car.kind() {
ValueKind::T => entries.push(InterpretedClosureEnvEntry::TopLevelSentinel),
ValueKind::Symbol(sym) => {
entries.push(InterpretedClosureEnvEntry::Special(sym))
}
ValueKind::Cons => {
let inner_car = pair_car.cons_car();
if let Some(sym) = binding_symbol_id(inner_car) {
entries.push(InterpretedClosureEnvEntry::Binding(sym));
}
}
_ => {}
}
cursor = pair_cdr;
}
_ => return entries,
}
}
}
fn binding_symbol_id(value: Value) -> Option<SymId> {
match value.kind() {
ValueKind::Symbol(sym) => Some(sym),
ValueKind::T => Some(intern("t")),
ValueKind::Nil => Some(intern("nil")),
_ => None,
}
}
fn interpreted_closure_trim_fingerprint(
params_value: Value,
body_value: Value,
iform_value: Value,
env_shape: &[InterpretedClosureEnvEntry],
) -> u64 {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
let mut seen = std::collections::HashSet::new();
value_fingerprint(params_value, &mut hasher, 8, &mut seen);
value_fingerprint(body_value, &mut hasher, 8, &mut seen);
value_fingerprint(iform_value, &mut hasher, 8, &mut seen);
env_shape.hash(&mut hasher);
hasher.finish()
}
fn interpreted_closure_env_shape_hash(env_shape: &[InterpretedClosureEnvEntry]) -> u64 {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
env_shape.hash(&mut hasher);
hasher.finish()
}
fn rebuild_trimmed_interpreted_closure_env(
source_env: Value,
template: &[InterpretedClosureEnvEntry],
) -> Value {
let mut entries = Vec::with_capacity(template.len());
for entry in template {
match entry {
InterpretedClosureEnvEntry::TopLevelSentinel => entries.push(Value::T),
InterpretedClosureEnvEntry::Special(sym) => entries.push(Value::from_sym_id(*sym)),
InterpretedClosureEnvEntry::Binding(sym) => {
let cell = lexenv_assq(source_env, *sym)
.expect("cached interpreted-closure env binding should exist");
entries.push(cell);
}
}
}
Value::list(entries)
}
#[derive(Clone, Debug)]
enum NamedCallTarget {
Obarray(Value),
Subr(Value),
Void,
}
#[derive(Clone, Debug)]
struct NamedCallCacheEntry {
function_epoch: u64,
target: NamedCallTarget,
}
#[derive(Clone, Copy, Debug)]
struct LexenvAssqCacheEntry {
lexenv_bits: usize,
symbol: SymId,
cell: Value,
}
#[derive(Clone, Debug, Default)]
struct LexenvAssqCache {
entries: [Option<LexenvAssqCacheEntry>; LEXENV_ASSQ_CACHE_CAPACITY],
}
impl LexenvAssqCache {
#[inline]
fn slot(lexenv_bits: usize, sym_id: SymId) -> usize {
let mixed = lexenv_bits.rotate_left(7) ^ (sym_id.0 as usize).wrapping_mul(0x9E37_79B1);
mixed & (LEXENV_ASSQ_CACHE_CAPACITY - 1)
}
#[inline]
fn find(&self, lexenv_bits: usize, sym_id: SymId) -> Option<Value> {
let entry = self.entries[Self::slot(lexenv_bits, sym_id)]?;
(entry.lexenv_bits == lexenv_bits && entry.symbol == sym_id).then_some(entry.cell)
}
#[inline]
fn push(&mut self, entry: LexenvAssqCacheEntry) {
let index = Self::slot(entry.lexenv_bits, entry.symbol);
self.entries[index] = Some(entry);
}
}
#[derive(Clone, Copy, Debug)]
struct LexenvSpecialCacheEntry {
lexenv_bits: usize,
symbol: SymId,
declared_special: bool,
}
#[derive(Clone, Debug, Default)]
struct LexenvSpecialCache {
entries: [Option<LexenvSpecialCacheEntry>; LEXENV_SPECIAL_CACHE_CAPACITY],
}
impl LexenvSpecialCache {
#[inline]
fn slot(lexenv_bits: usize, sym_id: SymId) -> usize {
let mixed = lexenv_bits.rotate_left(7) ^ (sym_id.0 as usize).wrapping_mul(0x9E37_79B1);
mixed & (LEXENV_SPECIAL_CACHE_CAPACITY - 1)
}
#[inline]
fn find(&self, lexenv_bits: usize, sym_id: SymId) -> Option<bool> {
let entry = self.entries[Self::slot(lexenv_bits, sym_id)]?;
(entry.lexenv_bits == lexenv_bits && entry.symbol == sym_id)
.then_some(entry.declared_special)
}
#[inline]
fn push(&mut self, entry: LexenvSpecialCacheEntry) {
let index = Self::slot(entry.lexenv_bits, entry.symbol);
self.entries[index] = Some(entry);
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
enum InterpretedClosureEnvEntry {
TopLevelSentinel,
Special(SymId),
Binding(SymId),
}
#[derive(Clone, Debug)]
pub(crate) struct RuntimeMacroExpansionCacheEntry {
function: Value,
expanded: Value,
fingerprint: u64,
}
impl RuntimeMacroExpansionCacheEntry {
fn new(function: Value, expanded: Value, fingerprint: u64) -> Self {
Self {
function,
expanded,
fingerprint,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
struct MacroPerfCounter {
calls: u64,
total_us: u64,
max_us: u64,
}
impl MacroPerfCounter {
fn note_duration(&mut self, duration: std::time::Duration) {
let elapsed_us = duration.as_micros() as u64;
self.calls = self.calls.saturating_add(1);
self.total_us = self.total_us.saturating_add(elapsed_us);
self.max_us = self.max_us.max(elapsed_us);
}
fn summary(&self, label: &str) -> Option<String> {
if self.calls == 0 {
return None;
}
let avg_us = self.total_us / self.calls.max(1);
Some(format!(
"{label}=count:{} total:{:.2}ms avg:{:.3}ms max:{:.3}ms",
self.calls,
self.total_us as f64 / 1000.0,
avg_us as f64 / 1000.0,
self.max_us as f64 / 1000.0
))
}
}
#[derive(Clone, Debug, Default)]
struct MacroPerfStats {
scope_enter: MacroPerfCounter,
scope_exit: MacroPerfCounter,
macro_apply: MacroPerfCounter,
cache_lookup: MacroPerfCounter,
cache_store: MacroPerfCounter,
expand_macro: MacroPerfCounter,
eager_step1: MacroPerfCounter,
eager_step3: MacroPerfCounter,
eager_step4: MacroPerfCounter,
}
#[derive(Clone, Debug)]
struct InterpretedClosureTrimCacheEntry {
params_value: Value,
body_value: Value,
iform_value: Value,
env_shape: Vec<InterpretedClosureEnvEntry>,
trimmed_params_value: Value,
trimmed_body_value: Value,
trimmed_env_template: Vec<InterpretedClosureEnvEntry>,
}
impl InterpretedClosureTrimCacheEntry {
fn matches(
&self,
params_value: Value,
body_value: Value,
iform_value: Value,
env_shape: &[InterpretedClosureEnvEntry],
) -> bool {
equal_value(&self.params_value, ¶ms_value, 0)
&& equal_value(&self.body_value, &body_value, 0)
&& equal_value(&self.iform_value, &iform_value, 0)
&& self.env_shape == env_shape
}
}
#[derive(Clone, Debug)]
struct InterpretedClosureValueCacheEntry {
source_function: Value,
env_shape: Vec<InterpretedClosureEnvEntry>,
trimmed_params_value: Value,
trimmed_body_value: Value,
trimmed_env_template: Vec<InterpretedClosureEnvEntry>,
}
impl InterpretedClosureValueCacheEntry {
fn matches(&self, source_function: Value, env_shape: &[InterpretedClosureEnvEntry]) -> bool {
equal_value(&self.source_function, &source_function, 0) && self.env_shape == env_shape
}
}
fn value_from_symbol_id(sym_id: SymId) -> Value {
if is_canonical_id(sym_id) {
if sym_id == nil_symbol() {
return Value::NIL;
}
if sym_id == t_symbol() {
return Value::T;
}
if is_keyword_id(sym_id) {
return Value::from_kw_id(sym_id);
}
}
Value::from_sym_id(sym_id)
}
fn hidden_internal_interpreter_environment_symbol() -> SymId {
static HIDDEN_SYMBOL: OnceLock<SymId> = OnceLock::new();
*HIDDEN_SYMBOL.get_or_init(|| intern_uninterned("internal-interpreter-environment"))
}
fn default_directory_symbol() -> SymId {
static SYMBOL: OnceLock<SymId> = OnceLock::new();
*SYMBOL.get_or_init(|| intern("default-directory"))
}
fn lexical_binding_symbol() -> SymId {
static SYMBOL: OnceLock<SymId> = OnceLock::new();
*SYMBOL.get_or_init(|| intern("lexical-binding"))
}
fn nil_symbol() -> SymId {
static SYMBOL: OnceLock<SymId> = OnceLock::new();
*SYMBOL.get_or_init(|| intern("nil"))
}
fn t_symbol() -> SymId {
static SYMBOL: OnceLock<SymId> = OnceLock::new();
*SYMBOL.get_or_init(|| intern("t"))
}
fn buffer_undo_list_symbol() -> SymId {
static SYMBOL: OnceLock<SymId> = OnceLock::new();
*SYMBOL.get_or_init(|| intern("buffer-undo-list"))
}
fn macroexp_dynvars_symbol() -> SymId {
static SYMBOL: OnceLock<SymId> = OnceLock::new();
*SYMBOL.get_or_init(|| intern("macroexp--dynvars"))
}
macro_rules! cached_symbol_id {
($fn_name:ident, $name:literal) => {
#[inline(always)]
fn $fn_name() -> SymId {
static SYMBOL: OnceLock<SymId> = OnceLock::new();
if let Some(id) = SYMBOL.get() {
*id
} else {
*SYMBOL.get_or_init(|| intern($name))
}
}
};
}
cached_symbol_id!(quote_symbol, "quote");
cached_symbol_id!(function_symbol, "function");
cached_symbol_id!(let_symbol, "let");
cached_symbol_id!(let_star_symbol, "let*");
cached_symbol_id!(setq_symbol, "setq");
cached_symbol_id!(if_symbol, "if");
cached_symbol_id!(and_symbol, "and");
cached_symbol_id!(or_symbol, "or");
cached_symbol_id!(cond_symbol, "cond");
cached_symbol_id!(while_symbol, "while");
cached_symbol_id!(progn_symbol, "progn");
cached_symbol_id!(prog1_symbol, "prog1");
cached_symbol_id!(defvar_symbol, "defvar");
cached_symbol_id!(defconst_symbol, "defconst");
cached_symbol_id!(catch_symbol, "catch");
cached_symbol_id!(unwind_protect_symbol, "unwind-protect");
cached_symbol_id!(condition_case_symbol, "condition-case");
cached_symbol_id!(save_excursion_symbol, "save-excursion");
cached_symbol_id!(save_current_buffer_symbol, "save-current-buffer");
cached_symbol_id!(save_restriction_symbol, "save-restriction");
cached_symbol_id!(interactive_symbol_id, "interactive");
cached_symbol_id!(lambda_symbol, "lambda");
cached_symbol_id!(closure_symbol, "closure");
cached_symbol_id!(declare_symbol, "declare");
cached_symbol_id!(macro_symbol, "macro");
cached_symbol_id!(byte_code_literal_symbol, "byte-code-literal");
cached_symbol_id!(byte_code_symbol, "byte-code");
cached_symbol_id!(gc_cons_threshold_symbol, "gc-cons-threshold");
cached_symbol_id!(gc_cons_percentage_symbol, "gc-cons-percentage");
cached_symbol_id!(memory_full_symbol, "memory-full");
cached_symbol_id!(gc_elapsed_symbol, "gc-elapsed");
cached_symbol_id!(gcs_done_symbol, "gcs-done");
fn is_lambda_like_symbol_id(id: SymId) -> bool {
id == lambda_symbol() || id == closure_symbol()
}
fn cons_head_symbol_id(value: &Value) -> Option<SymId> {
if value.is_cons() {
let car = value.cons_car();
car.as_symbol_id().or_else(|| {
car.as_symbol_with_pos_sym()
.and_then(|sym| sym.as_symbol_id())
})
} else {
None
}
}
struct CoreEvalSymbols {
internal_interpreter_environment_symbol: SymId,
quit_flag_symbol: SymId,
inhibit_quit_symbol: SymId,
throw_on_input_symbol: SymId,
kill_emacs_symbol: SymId,
noninteractive_symbol: SymId,
symbols_with_pos_enabled_symbol: SymId,
print_symbols_bare_symbol: SymId,
}
fn install_core_eval_symbols(obarray: &mut Obarray, reset_runtime_values: bool) -> CoreEvalSymbols {
obarray.intern("internal-interpreter-environment");
let internal_interpreter_environment_symbol = hidden_internal_interpreter_environment_symbol();
obarray.set_symbol_value_id(internal_interpreter_environment_symbol, Value::NIL);
obarray.make_special_id(internal_interpreter_environment_symbol);
let quit_flag_symbol = intern("quit-flag");
if reset_runtime_values {
obarray.set_symbol_value_id(quit_flag_symbol, Value::NIL);
}
obarray.make_special_id(quit_flag_symbol);
let inhibit_quit_symbol = intern("inhibit-quit");
if reset_runtime_values {
obarray.set_symbol_value_id(inhibit_quit_symbol, Value::NIL);
}
obarray.make_special_id(inhibit_quit_symbol);
let throw_on_input_symbol = intern("throw-on-input");
if reset_runtime_values {
obarray.set_symbol_value_id(throw_on_input_symbol, Value::NIL);
}
obarray.make_special_id(throw_on_input_symbol);
let kill_emacs_symbol = intern("kill-emacs");
let noninteractive_symbol = intern("noninteractive");
let symbols_with_pos_enabled_symbol = intern("symbols-with-pos-enabled");
let print_symbols_bare_symbol = intern("print-symbols-bare");
CoreEvalSymbols {
internal_interpreter_environment_symbol,
quit_flag_symbol,
inhibit_quit_symbol,
throw_on_input_symbol,
kill_emacs_symbol,
noninteractive_symbol,
symbols_with_pos_enabled_symbol,
print_symbols_bare_symbol,
}
}
fn is_runtime_dynamically_special(obarray: &Obarray, sym_id: SymId) -> bool {
obarray.is_special_id(sym_id) && !obarray.is_constant_id(sym_id)
}
fn symbol_sets_constant_error(sym_id: SymId) -> Option<&'static str> {
match resolve_sym(sym_id) {
"nil" => Some("nil"),
"t" => Some("t"),
_ => None,
}
}
pub(crate) fn sync_features_variable_in_state(obarray: &mut Obarray, features: &[SymId]) {
let values: Vec<Value> = features.iter().map(|id| Value::from_sym_id(*id)).collect();
obarray.set_symbol_value("features", Value::list(values));
}
pub(crate) fn refresh_features_from_variable_in_state(
obarray: &Obarray,
features: &mut Vec<SymId>,
) {
let current = obarray
.symbol_value("features")
.cloned()
.unwrap_or(Value::NIL);
let mut parsed = Vec::new();
if let Some(items) = list_to_vec(¤t) {
for item in items {
if let Some(id) = item.as_symbol_id() {
parsed.push(id);
}
}
}
*features = parsed;
}
pub(crate) fn feature_present_in_state(
obarray: &Obarray,
features: &mut Vec<SymId>,
name: &str,
) -> bool {
refresh_features_from_variable_in_state(obarray, features);
let id = intern(name);
features.iter().any(|feature| *feature == id)
}
pub(crate) fn add_feature_in_state(obarray: &mut Obarray, features: &mut Vec<SymId>, name: &str) {
refresh_features_from_variable_in_state(obarray, features);
let id = intern(name);
if features.iter().any(|feature| *feature == id) {
return;
}
features.insert(0, id);
sync_features_variable_in_state(obarray, features);
}
pub(crate) fn remove_feature_in_state(
obarray: &mut Obarray,
features: &mut Vec<SymId>,
name: &str,
) {
refresh_features_from_variable_in_state(obarray, features);
let id = intern(name);
features.retain(|feature| *feature != id);
sync_features_variable_in_state(obarray, features);
}
pub(crate) fn provide_value_in_state(
obarray: &mut Obarray,
features: &mut Vec<SymId>,
feature: Value,
subfeatures: Option<Value>,
) -> EvalResult {
let sym_id = super::builtins::symbols::symbol_id(&feature).ok_or_else(|| {
signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), feature],
)
})?;
let name = resolve_sym(sym_id).to_owned();
if let Some(value) = subfeatures {
obarray.put_property(&name, "subfeatures", value)?;
}
add_feature_in_state(obarray, features, &name);
Ok(feature)
}
pub(crate) const RECENT_INPUT_EVENT_LIMIT: usize = 300;
thread_local! {
static SCRATCH_GC_ROOTS: RefCell<Vec<Value>> = const { RefCell::new(Vec::new()) };
}
fn collect_thread_local_gc_roots(roots: &mut Vec<Value>) {
super::syntax::collect_syntax_gc_roots(roots);
super::casetab::collect_casetab_gc_roots(roots);
super::category::collect_category_gc_roots(roots);
super::value_reader::collect_value_reader_gc_roots(roots);
super::terminal::pure::collect_terminal_gc_roots(roots);
super::font::collect_font_gc_roots(roots);
super::charset::collect_charset_gc_roots(roots);
super::ccl::collect_ccl_gc_roots(roots);
SCRATCH_GC_ROOTS.with(|scratch| roots.extend(scratch.borrow().iter().copied()));
}
pub fn save_scratch_gc_roots() -> usize {
SCRATCH_GC_ROOTS.with(|scratch| scratch.borrow().len())
}
pub fn push_scratch_gc_root(value: Value) {
SCRATCH_GC_ROOTS.with(|scratch| scratch.borrow_mut().push(value));
}
pub fn restore_scratch_gc_roots(saved_len: usize) {
SCRATCH_GC_ROOTS.with(|scratch| scratch.borrow_mut().truncate(saved_len));
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GuiFrameHostRequest {
pub frame_id: crate::window::FrameId,
pub width: u32,
pub height: u32,
pub title: crate::heap_types::LispString,
pub geometry_hints: crate::window::GuiFrameGeometryHints,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GuiFrameHostSize {
pub width: u32,
pub height: u32,
}
#[derive(Clone, Debug)]
pub struct FontResolveRequest {
pub frame_id: crate::window::FrameId,
pub character: char,
pub face: RuntimeFace,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FontSpecResolveRequest {
pub frame_id: crate::window::FrameId,
pub family: Option<crate::heap_types::LispString>,
pub registry: Option<crate::heap_types::LispString>,
pub lang: Option<crate::heap_types::LispString>,
pub weight: Option<FontWeight>,
pub slant: Option<FontSlant>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolvedFontMatch {
pub family: crate::heap_types::LispString,
pub foundry: Option<crate::heap_types::LispString>,
pub weight: FontWeight,
pub slant: FontSlant,
pub width: FontWidth,
pub postscript_name: Option<crate::heap_types::LispString>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ResolvedFrameFont {
pub family: crate::heap_types::LispString,
pub foundry: Option<crate::heap_types::LispString>,
pub weight: FontWeight,
pub slant: FontSlant,
pub width: FontWidth,
pub postscript_name: Option<crate::heap_types::LispString>,
pub font_size_px: f32,
pub char_width: f32,
pub line_height: f32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolvedFontSpecMatch {
pub family: crate::heap_types::LispString,
pub registry: Option<crate::heap_types::LispString>,
pub weight: Option<FontWeight>,
pub slant: Option<FontSlant>,
pub width: Option<FontWidth>,
pub spacing: Option<i32>,
pub postscript_name: Option<crate::heap_types::LispString>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ImageResolveSource {
File(crate::heap_types::LispString),
Data(Vec<u8>),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ImageResolveRequest {
pub source: ImageResolveSource,
pub max_width: u32,
pub max_height: u32,
pub fg_color: u32,
pub bg_color: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolvedImage {
pub image_id: u32,
pub width: u32,
pub height: u32,
}
pub trait DisplayHost {
fn realize_gui_frame(&mut self, request: GuiFrameHostRequest) -> Result<(), String>;
fn resize_gui_frame(&mut self, request: GuiFrameHostRequest) -> Result<(), String>;
fn set_gui_frame_geometry_hints(
&mut self,
_frame_id: crate::window::FrameId,
_geometry_hints: crate::window::GuiFrameGeometryHints,
) -> Result<(), String> {
Ok(())
}
fn set_gui_frame_title(
&mut self,
_frame_id: crate::window::FrameId,
_title: crate::heap_types::LispString,
) -> Result<(), String> {
Ok(())
}
fn opening_gui_frame_pending(&self) -> bool {
false
}
fn current_primary_window_size(&self) -> Option<GuiFrameHostSize> {
None
}
fn resolve_font_for_char(
&mut self,
_request: FontResolveRequest,
) -> Result<Option<ResolvedFontMatch>, String> {
Ok(None)
}
fn resolve_frame_font(
&mut self,
_frame_id: crate::window::FrameId,
_face: RuntimeFace,
) -> Result<Option<ResolvedFrameFont>, String> {
Ok(None)
}
fn resolve_font_for_spec(
&mut self,
_request: FontSpecResolveRequest,
) -> Result<Option<ResolvedFontSpecMatch>, String> {
Ok(None)
}
fn resolve_image(
&self,
_request: ImageResolveRequest,
) -> Result<Option<ResolvedImage>, String> {
Ok(None)
}
fn set_cursor_blink(&mut self, _enabled: bool, _interval_ms: u32) -> Result<(), String> {
Ok(())
}
}
unsafe impl Send for Context {}
#[derive(Clone, Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub(crate) enum ResumeTarget {
CommandLoopExit,
CommandLoopTopLevel,
InterpreterCatch,
InterpreterConditionCase {
handler_index: usize,
condition_stack_base: usize,
},
VmCatch {
resume_id: u64,
target: u32,
stack_len: usize,
spec_depth: usize,
bind_stack_len: usize,
},
VmConditionCase {
resume_id: u64,
target: u32,
stack_len: usize,
spec_depth: usize,
bind_stack_len: usize,
},
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub(crate) enum ConditionFrame {
Catch {
tag: Value,
resume: ResumeTarget,
},
ConditionCase {
conditions: Value,
resume: ResumeTarget,
},
HandlerBind {
conditions: Value,
handler: Value,
mute_span: usize,
},
SkipConditions {
remaining: usize,
},
}
fn condition_value_contains_debug(value: &Value) -> bool {
match value.kind() {
ValueKind::Symbol(id) => resolve_sym(id) == "debug",
ValueKind::Cons => {
list_to_vec(value).is_some_and(|items| items.iter().any(condition_value_contains_debug))
}
_ => false,
}
}
fn wants_debugger(setting: &Value, conditions: &Value) -> bool {
if setting.is_nil() {
return false;
}
let Some(entries) = list_to_vec(setting) else {
return true;
};
let signal_conditions = list_to_vec(conditions).unwrap_or_else(|| vec![*conditions]);
entries
.iter()
.any(|entry| signal_conditions.iter().any(|condition| condition == entry))
}
fn signal_hook_payload_value(sig: &SignalData) -> Value {
if let Some(raw) = &sig.raw_data {
*raw
} else if sig.data.is_empty() {
Value::NIL
} else {
Value::list(sig.data.clone())
}
}
pub(crate) struct BcFrame {
pub base: usize,
pub fun: Value,
}
pub struct Context {
pub(crate) tagged_heap: Box<crate::tagged::gc::TaggedHeap>,
pub(crate) pdump_image: Option<super::pdump::mmap_image::LoadedMmapImage>,
pub(crate) obarray: Obarray,
pub(crate) specpdl: Vec<SpecBinding>,
pub(crate) lexenv: Value,
pub(crate) internal_interpreter_environment_symbol: SymId,
quit_flag_symbol: SymId,
inhibit_quit_symbol: SymId,
throw_on_input_symbol: SymId,
kill_emacs_symbol: SymId,
quit_flag: Value,
inhibit_quit: Value,
noninteractive_symbol: SymId,
noninteractive: bool,
symbols_with_pos_enabled_symbol: SymId,
pub(crate) symbols_with_pos_enabled: bool,
print_symbols_bare_symbol: SymId,
pub(crate) print_symbols_bare: bool,
pub(crate) features: Vec<SymId>,
pub(crate) require_stack: Vec<SymId>,
pub(crate) loads_in_progress: Vec<crate::heap_types::LispString>,
pub buffers: BufferManager,
pub(crate) match_data: Option<MatchData>,
pub(crate) processes: ProcessManager,
pub(crate) timers: TimerManager,
pub(crate) watchers: VariableWatcherList,
pub(crate) standard_syntax_table: Value,
pub(crate) standard_category_table: Value,
pub(crate) current_local_map: Value,
pub(crate) registers: RegisterManager,
pub(crate) bookmarks: BookmarkManager,
pub(crate) abbrevs: AbbrevManager,
pub(crate) autoloads: AutoloadManager,
pub(crate) custom: CustomManager,
pub(crate) rectangle: RectangleState,
pub(crate) interactive: InteractiveRegistry,
pub(crate) treesit: super::treesit::TreeSitterManager,
pub(crate) minibuffers: MinibufferManager,
pub(crate) interactive_minibuffer_read_count: u64,
pub(crate) current_message: Option<crate::heap_types::LispString>,
pub(crate) minibuffer_selected_window: Option<crate::window::WindowId>,
pub(crate) active_minibuffer_window: Option<crate::window::WindowId>,
pub(crate) shutdown_request: Option<ShutdownRequest>,
pub(crate) input_mode_interrupt: bool,
pub(crate) quit_char: i64,
pub(crate) waiting_for_user_input: bool,
pub(crate) frames: FrameManager,
pub(crate) modes: ModeRegistry,
pub(crate) threads: ThreadManager,
pub(crate) kmacro: KmacroManager,
pub(crate) command_loop: crate::keyboard::CommandLoop,
pub input_rx: Option<crossbeam_channel::Receiver<crate::keyboard::InputEvent>>,
#[cfg(unix)]
pub wakeup_fd: Option<std::os::unix::io::RawFd>,
pub quit_requested: std::sync::Arc<std::sync::atomic::AtomicBool>,
pub redisplay_fn: Option<Box<dyn FnMut(&mut Self)>>,
pub display_host: Option<Box<dyn DisplayHost>>,
pub(crate) coding_systems: CodingSystemManager,
pub(crate) face_table: FaceTable,
pub face_change_count: u64,
redisplay_generation: u64,
last_redisplay_signature: Option<RedisplaySignature>,
pub(crate) depth: usize,
eval_counter: u64,
pub(crate) max_depth: usize,
pub(crate) gc_pending: bool,
pub(crate) gc_count: u64,
pub(crate) gc_inhibit_depth: usize,
pub(crate) gc_stress: bool,
gc_runtime_settings_cache: GcRuntimeSettingsCache,
vm_root_frames: Vec<VmRootFrame>,
pub(crate) bc_buf: Vec<Value>,
pub(crate) bc_frames: Vec<BcFrame>,
pub(crate) condition_stack: Vec<ConditionFrame>,
next_resume_id: u64,
pub(crate) pending_safe_funcalls: Vec<PendingSafeFuncall>,
named_call_cache: HashMap<SymId, NamedCallCacheEntry>,
lexenv_assq_cache: RefCell<LexenvAssqCache>,
lexenv_special_cache: RefCell<LexenvSpecialCache>,
macro_expansion_scope_depth: usize,
macro_expansion_mutation_epoch: u64,
pub(crate) macro_cache_hits: u64,
pub(crate) macro_cache_misses: u64,
pub(crate) macro_expand_total_us: u64,
pub(crate) macro_cache_disabled: bool,
pub(crate) runtime_macro_expansion_cache:
HashMap<(usize, usize, u64), RuntimeMacroExpansionCacheEntry>,
macro_perf_enabled: bool,
macro_perf_stats: MacroPerfStats,
interpreted_closure_filter_fn: Option<Value>,
interpreted_closure_trim_cache: HashMap<u64, Vec<InterpretedClosureTrimCacheEntry>>,
interpreted_closure_value_cache: HashMap<(u64, u64), Vec<InterpretedClosureValueCacheEntry>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ShutdownRequest {
pub exit_code: i32,
pub restart: bool,
}
#[derive(Clone, Copy, Debug)]
struct GcRuntimeSettingsCache {
gc_cons_threshold_bytes: usize,
gc_cons_percentage_scaled: Option<u64>,
memory_full: bool,
}
impl Default for GcRuntimeSettingsCache {
fn default() -> Self {
Self {
gc_cons_threshold_bytes: GC_DEFAULT_THRESHOLD_BYTES,
gc_cons_percentage_scaled: Some(100_000),
memory_full: false,
}
}
}
pub(crate) enum RequirePlan {
Return(Value),
Load {
sym_id: SymId,
name: String,
path: std::path::PathBuf,
},
}
pub(crate) fn plan_require_in_state(
obarray: &Obarray,
features: &mut Vec<SymId>,
require_stack: &[SymId],
feature: Value,
filename: Option<Value>,
noerror: Option<Value>,
) -> Result<RequirePlan, Flow> {
refresh_features_from_variable_in_state(obarray, features);
let sym_id = super::builtins::symbols::symbol_id(&feature).ok_or_else(|| {
signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), feature],
)
})?;
let name = resolve_sym(sym_id).to_owned();
if features.contains(&sym_id) {
return Ok(RequirePlan::Return(Value::symbol(&name)));
}
let nesting = require_stack
.iter()
.filter(|stacked_sym_id| **stacked_sym_id == sym_id)
.count();
if nesting > 3 {
return Err(signal(
"error",
vec![Value::string(format!(
"Recursive `require' for feature `{name}'"
))],
));
}
let filename = match filename {
Some(v) if v.is_nil() => name.clone(),
Some(v) if v.is_string() => runtime_string_value(v),
Some(other) => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), other],
));
}
None => name.clone(),
};
let filename = super::load::expand_tilde(&filename);
let load_path = super::load::get_load_path(obarray);
match super::load::find_file_in_load_path(&filename, &load_path) {
Some(path) => Ok(RequirePlan::Load { sym_id, name, path }),
None => {
if noerror.is_some_and(|value| value.is_truthy()) {
return Ok(RequirePlan::Return(Value::NIL));
}
Err(signal(
"file-missing",
vec![Value::string(format!(
"Cannot open load file: no such file or directory, {}",
name
))],
))
}
}
}
pub(crate) fn finish_require_in_state(features: &[SymId], sym_id: SymId, name: &str) -> EvalResult {
if features.contains(&sym_id) {
Ok(Value::symbol(name))
} else {
Err(signal(
"error",
vec![Value::string(format!(
"Required feature '{}' was not provided",
name
))],
))
}
}
pub(crate) fn parse_eval_lexical_arg(arg: Option<Value>) -> Result<(bool, Option<Value>), Flow> {
let Some(arg) = arg else {
return Ok((false, Some(Value::NIL)));
};
if arg.is_nil() {
return Ok((false, Some(Value::NIL)));
}
if !arg.is_cons() {
return Ok((true, Some(Value::list(vec![Value::T]))));
};
if list_to_vec(&arg).is_none() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("listp"), arg],
));
}
Ok((true, Some(arg)))
}
fn lexical_binding_in_obarray(obarray: &Obarray) -> bool {
obarray
.symbol_value_id(lexical_binding_symbol())
.is_some_and(|v| v.is_truthy())
}
#[inline]
fn top_level_lexenv_sentinel() -> Value {
Value::list(vec![Value::T])
}
#[inline]
fn lexenv_is_active(lexenv: Value) -> bool {
!lexenv.is_nil()
}
#[inline]
fn is_top_level_lexenv_sentinel(lexenv: Value) -> bool {
lexenv.is_cons() && lexenv.cons_car().is_t() && lexenv.cons_cdr().is_nil()
}
pub(crate) struct ActiveEvalLexicalArgState {
specpdl_count: usize,
}
pub(crate) fn begin_eval_with_lexical_arg_in_state(
_obarray: &mut Obarray,
lexenv: &mut Value,
specpdl: &mut Vec<SpecBinding>,
lexical_arg: Option<Value>,
) -> Result<ActiveEvalLexicalArgState, Flow> {
let (_use_lexical, lexenv_value) = parse_eval_lexical_arg(lexical_arg)?;
let specpdl_count = specpdl.len();
if let Some(env) = lexenv_value {
specpdl.push(SpecBinding::LexicalEnv {
old_lexenv: *lexenv,
});
*lexenv = env;
}
Ok(ActiveEvalLexicalArgState { specpdl_count })
}
pub(crate) fn finish_eval_with_lexical_arg_in_state(
_obarray: &mut Obarray,
lexenv: &mut Value,
specpdl: &mut Vec<SpecBinding>,
state: ActiveEvalLexicalArgState,
) {
while specpdl.len() > state.specpdl_count {
let binding = specpdl.pop().unwrap();
match binding {
SpecBinding::LexicalEnv { old_lexenv } => {
*lexenv = old_lexenv;
}
other => {
specpdl.push(other);
break;
}
}
}
}
pub(crate) struct ActiveLambdaCallState {
specpdl_count: usize,
}
pub(crate) struct ActiveMacroExpansionScopeState {
saved_specpdl_len: usize,
old_lexical: bool,
old_dynvars: Value,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct VmRootScopeState {
pushed_vm_root_frame: bool,
saved_vm_root_frame_len: Option<usize>,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct SpecpdlRootScopeState {
saved_len: usize,
}
fn bind_lexical_value_rooted_in_specpdl(
lexenv: &mut Value,
specpdl: &mut Vec<SpecBinding>,
sym: SymId,
value: Value,
) {
specpdl.push(SpecBinding::GcRoot { value });
let binding = Value::make_cons(lexenv_binding_symbol_value(sym), value);
match specpdl.last_mut() {
Some(SpecBinding::GcRoot { value }) => *value = binding,
other => panic!("expected temporary specpdl gc root entry, got {other:?}"),
}
*lexenv = Value::make_cons(binding, *lexenv);
match specpdl.pop() {
Some(SpecBinding::GcRoot { .. }) => {}
other => panic!("expected temporary specpdl gc root entry, got {other:?}"),
}
}
fn prepend_lexical_binding_in_specpdl_rooted_env(
lexenv: &mut Value,
specpdl: &mut Vec<SpecBinding>,
env_root_index: usize,
sym: SymId,
value: Value,
) {
specpdl.push(SpecBinding::GcRoot { value });
let current_env = match specpdl.get(env_root_index) {
Some(SpecBinding::GcRoot { value }) => *value,
other => panic!("expected specpdl gc root entry for lexical env, got {other:?}"),
};
let binding = Value::make_cons(lexenv_binding_symbol_value(sym), value);
match specpdl.last_mut() {
Some(SpecBinding::GcRoot { value }) => *value = binding,
other => panic!("expected temporary specpdl gc root entry, got {other:?}"),
}
let new_env = Value::make_cons(binding, current_env);
match specpdl.get_mut(env_root_index) {
Some(SpecBinding::GcRoot { value }) => *value = new_env,
other => panic!("expected mutable specpdl gc root entry for lexical env, got {other:?}"),
}
*lexenv = new_env;
match specpdl.pop() {
Some(SpecBinding::GcRoot { .. }) => {}
other => panic!("expected temporary specpdl gc root entry, got {other:?}"),
}
}
fn lambda_arity_cons(params: &LambdaParams) -> Value {
let min_val = Value::fixnum(params.min_arity() as i64);
let max_val = match params.max_arity() {
Some(n) => Value::fixnum(n as i64),
None => Value::symbol("many"),
};
Value::cons(min_val, max_val)
}
fn begin_lambda_call_in_state(
obarray: &mut Obarray,
specpdl: &mut Vec<SpecBinding>,
lexenv: &mut Value,
params: &LambdaParams,
env: Option<Value>,
args: &[Value],
) -> Result<ActiveLambdaCallState, Flow> {
if args.len() < params.min_arity() {
let arity_val = lambda_arity_cons(params);
return Err(signal(
"wrong-number-of-arguments",
vec![arity_val, Value::fixnum(args.len() as i64)],
));
}
if let Some(max) = params.max_arity()
&& args.len() > max
{
let arity_val = lambda_arity_cons(params);
return Err(signal(
"wrong-number-of-arguments",
vec![arity_val, Value::fixnum(args.len() as i64)],
));
}
let specpdl_count = specpdl.len();
if let Some(env) = env {
if env.is_t() {
tracing::error!(
"Lambda called with env=t (should be (t))! params={:?}",
params
);
}
let old = std::mem::replace(lexenv, env);
specpdl.push(SpecBinding::LexicalEnv { old_lexenv: old });
let env_root_index = specpdl.len();
specpdl.push(SpecBinding::GcRoot { value: env });
let mut arg_idx = 0;
for param in ¶ms.required {
prepend_lexical_binding_in_specpdl_rooted_env(
lexenv,
specpdl,
env_root_index,
*param,
args[arg_idx],
);
arg_idx += 1;
}
for param in ¶ms.optional {
if arg_idx < args.len() {
prepend_lexical_binding_in_specpdl_rooted_env(
lexenv,
specpdl,
env_root_index,
*param,
args[arg_idx],
);
arg_idx += 1;
} else {
prepend_lexical_binding_in_specpdl_rooted_env(
lexenv,
specpdl,
env_root_index,
*param,
Value::NIL,
);
}
}
if let Some(rest_name) = params.rest {
let rest_value = Value::list_from_slice(&args[arg_idx..]);
prepend_lexical_binding_in_specpdl_rooted_env(
lexenv,
specpdl,
env_root_index,
rest_name,
rest_value,
);
}
} else {
let mut arg_idx = 0;
for param in ¶ms.required {
specbind_in_state(obarray, specpdl, *param, args[arg_idx]);
arg_idx += 1;
}
for param in ¶ms.optional {
if arg_idx < args.len() {
specbind_in_state(obarray, specpdl, *param, args[arg_idx]);
arg_idx += 1;
} else {
specbind_in_state(obarray, specpdl, *param, Value::NIL);
}
}
if let Some(rest_name) = params.rest {
let rest_value = Value::list_from_slice(&args[arg_idx..]);
specbind_in_state(obarray, specpdl, rest_name, rest_value);
}
}
Ok(ActiveLambdaCallState { specpdl_count })
}
fn finish_lambda_call_in_state(
obarray: &mut Obarray,
specpdl: &mut Vec<SpecBinding>,
lexenv: &mut Value,
state: ActiveLambdaCallState,
) {
while specpdl.len() > state.specpdl_count {
let binding = specpdl.pop().unwrap();
match binding {
SpecBinding::LexicalEnv { old_lexenv } => {
*lexenv = old_lexenv;
}
SpecBinding::Let { sym_id, old_value } => match old_value {
Some(val) => obarray.set_symbol_value_id(sym_id, val),
None => obarray.makunbound_id(sym_id),
},
SpecBinding::GcRoot { .. } => {}
SpecBinding::Backtrace { .. } => {}
other => {
specpdl.push(other);
break;
}
}
}
}
fn begin_macro_expansion_scope_in_state(
obarray: &mut Obarray,
specpdl: &mut Vec<SpecBinding>,
buffers: &mut BufferManager,
custom: &CustomManager,
lexenv: Value,
) -> ActiveMacroExpansionScopeState {
let saved_specpdl_len = specpdl.len();
let old_lexical = obarray
.symbol_value_id(lexical_binding_symbol())
.is_some_and(|value| value.is_truthy());
let old_dynvars = obarray
.symbol_value_id(macroexp_dynvars_symbol())
.cloned()
.unwrap_or(Value::NIL);
let dynvars_root_index = specpdl.len();
specpdl.push(SpecBinding::GcRoot { value: old_dynvars });
let mut dynvars = old_dynvars;
let mut cursor = lexenv;
while cursor.is_cons() {
let entry = cursor.cons_car();
if let Some(sym) = entry.as_symbol_id() {
dynvars = Value::cons(Value::from_sym_id(sym), dynvars);
match specpdl.get_mut(dynvars_root_index) {
Some(SpecBinding::GcRoot { value }) => *value = dynvars,
other => panic!("expected macro-expansion dynvars gc root, got {other:?}"),
}
}
cursor = cursor.cons_cdr();
}
obarray.set_symbol_value_id(lexical_binding_symbol(), Value::bool_val(!lexenv.is_nil()));
set_runtime_binding(
obarray,
buffers,
custom,
specpdl,
macroexp_dynvars_symbol(),
dynvars,
);
ActiveMacroExpansionScopeState {
saved_specpdl_len,
old_lexical,
old_dynvars,
}
}
fn finish_macro_expansion_scope_in_state(
obarray: &mut Obarray,
specpdl: &mut Vec<SpecBinding>,
buffers: &mut BufferManager,
custom: &CustomManager,
state: ActiveMacroExpansionScopeState,
) {
set_runtime_binding(
obarray,
buffers,
custom,
specpdl,
macroexp_dynvars_symbol(),
state.old_dynvars,
);
obarray.set_symbol_value_id(lexical_binding_symbol(), Value::bool_val(state.old_lexical));
specpdl.truncate(state.saved_specpdl_len);
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
impl Context {
#[inline]
pub(crate) fn subr_dispatch_kind(&self, sym_id: SymId) -> Option<SubrDispatchKind> {
lookup_global_subr_entry(sym_id).map(|e| e.dispatch_kind)
}
#[inline]
pub(crate) fn subr_dispatch_kind_or_compat(&self, sym_id: SymId) -> SubrDispatchKind {
self.subr_dispatch_kind(sym_id)
.unwrap_or_else(|| super::subr_info::compat_subr_dispatch_kind(resolve_sym(sym_id)))
}
#[inline]
fn subr_is_special_form_id(&self, sym_id: SymId) -> bool {
self.subr_dispatch_kind_or_compat(sym_id) == SubrDispatchKind::SpecialForm
}
#[inline]
fn subr_is_context_callable_id(&self, sym_id: SymId) -> bool {
self.subr_dispatch_kind_or_compat(sym_id) == SubrDispatchKind::ContextCallable
}
#[inline]
fn has_registered_subr(&self, sym_id: SymId) -> bool {
lookup_global_subr_entry(sym_id).is_some_and(|e| e.function.is_some())
}
pub fn new() -> Self {
let mut ctx = Self::new_inner(true);
ctx.initialize_gc_stack_bottom();
builtins::init_builtins(&mut ctx);
ctx
}
#[cfg(test)]
pub(crate) fn new_vm_runtime_harness() -> Self {
Self::new()
}
#[cfg(test)]
pub(crate) fn new_minimal_vm_harness() -> Self {
let mut ev = Self::new_inner(true);
ev.obarray = Obarray::new();
super::errors::init_standard_errors(&mut ev.obarray);
ev.obarray
.set_symbol_value("most-positive-fixnum", Value::fixnum(i64::MAX >> 2));
ev.obarray
.set_symbol_value("most-negative-fixnum", Value::fixnum(-(i64::MAX >> 2) - 1));
ev.specpdl.clear();
ev.lexenv = Value::NIL;
ev.features.clear();
ev.require_stack.clear();
ev.loads_in_progress.clear();
ev.buffers = BufferManager::new();
ev.match_data = None;
ev.processes = ProcessManager::new();
ev.timers = TimerManager::new();
ev.watchers = VariableWatcherList::new();
ev.current_local_map = Value::NIL;
ev.registers = RegisterManager::new();
ev.bookmarks = BookmarkManager::new();
ev.abbrevs = AbbrevManager::new();
ev.autoloads = AutoloadManager::new();
ev.custom = CustomManager::new();
ev.rectangle = RectangleState::new();
ev.interactive = InteractiveRegistry::new();
ev.input_mode_interrupt = false;
ev.frames = FrameManager::new();
ev.modes = ModeRegistry::new();
ev.threads = ThreadManager::new();
ev.kmacro = KmacroManager::new();
ev.command_loop = crate::keyboard::CommandLoop::default();
ev.input_rx = None;
#[cfg(unix)]
{
ev.wakeup_fd = None;
}
ev.redisplay_fn = None;
ev.display_host = None;
ev.coding_systems = CodingSystemManager::new();
ev.face_table = FaceTable::new();
ev.face_change_count = 0;
ev.redisplay_generation = 0;
ev.last_redisplay_signature = None;
ev.depth = 0;
ev.max_depth = 1600;
ev.gc_pending = false;
ev.gc_count = 0;
ev.gc_stress = false;
ev.condition_stack.clear();
ev.next_resume_id = 1;
ev.named_call_cache.clear();
ev.runtime_macro_expansion_cache.clear();
ev.macro_cache_hits = 0;
ev.macro_cache_misses = 0;
ev.macro_expand_total_us = 0;
ev.macro_cache_disabled = false;
ev.macro_perf_enabled = std::env::var_os("NEOVM_TRACE_MACRO_PERF").is_some();
ev.macro_perf_stats = MacroPerfStats::default();
ev.interpreted_closure_filter_fn = None;
ev.interpreted_closure_trim_cache.clear();
ev.materialize_public_evaluator_function_cells();
ev.finish_runtime_activation(false);
ev
}
pub(crate) fn push_condition_frame(&mut self, frame: ConditionFrame) {
self.condition_stack.push(frame);
}
pub(crate) fn pop_condition_frame(&mut self) -> Option<ConditionFrame> {
self.condition_stack.pop()
}
pub(crate) fn truncate_condition_stack(&mut self, len: usize) {
self.condition_stack.truncate(len);
}
pub(crate) fn condition_stack_len(&self) -> usize {
self.condition_stack.len()
}
pub(crate) fn allocate_resume_id(&mut self) -> u64 {
let resume_id = self.next_resume_id;
self.next_resume_id += 1;
resume_id
}
pub(crate) fn matching_catch_resume(&self, tag: &Value) -> Option<ResumeTarget> {
if tag.is_nil() {
return None;
}
self.condition_stack
.iter()
.rev()
.find_map(|frame| match frame {
ConditionFrame::Catch {
tag: catch_tag,
resume,
} if eq_value(catch_tag, tag) => Some(resume.clone()),
_ => None,
})
}
pub(crate) fn has_active_catch(&self, tag: &Value) -> bool {
self.matching_catch_resume(tag).is_some()
}
pub(crate) fn dispatch_signal_if_needed(
&mut self,
sig: Box<SignalData>,
) -> Result<Box<SignalData>, Flow> {
if sig.search_complete {
return Ok(sig);
}
self.dispatch_signal(*sig).map(Box::new)
}
pub(crate) fn dispatch_signal_result_if_needed(&mut self, result: EvalResult) -> EvalResult {
match result {
Err(Flow::Signal(sig)) => match self.dispatch_signal_if_needed(sig) {
Ok(dispatched) => Err(Flow::Signal(dispatched)),
Err(flow) => Err(flow),
},
other => other,
}
}
fn dispatch_signal(&mut self, mut sig: SignalData) -> Result<SignalData, Flow> {
if sig.symbol == self.kill_emacs_symbol {
return Err(Flow::Signal(Box::new(sig)));
}
self.run_signal_hook(&sig)?;
sig = self.canonicalize_signal_symbol(sig);
let mut idx = self.condition_stack.len();
let mut seen_condition_entries = 0usize;
while let Some(next_idx) = idx.checked_sub(1) {
idx = next_idx;
match self.condition_stack[idx].clone() {
ConditionFrame::Catch { .. } => {}
ConditionFrame::SkipConditions { remaining } => {
let mut to_skip = remaining;
while idx > 0 && to_skip > 0 {
idx -= 1;
if matches!(
self.condition_stack[idx],
ConditionFrame::ConditionCase { .. }
| ConditionFrame::HandlerBind { .. }
) {
to_skip -= 1;
}
}
}
ConditionFrame::ConditionCase { conditions, resume } => {
seen_condition_entries += 1;
if crate::emacs_core::errors::signal_matches_condition_value(
&self.obarray,
sig.symbol_name(),
&conditions,
) {
self.maybe_call_debugger_for_signal(&sig, Some(&conditions))?;
sig.selected_resume = Some(resume);
sig.search_complete = true;
return Ok(sig);
}
}
ConditionFrame::HandlerBind {
conditions,
handler,
mute_span,
} => {
seen_condition_entries += 1;
if !crate::emacs_core::errors::signal_matches_condition_value(
&self.obarray,
sig.symbol_name(),
&conditions,
) {
continue;
}
let specpdl_root_scope = self.save_specpdl_roots();
for value in &sig.data {
self.push_specpdl_root(*value);
}
if let Some(raw) = &sig.raw_data {
self.push_specpdl_root(*raw);
}
self.push_condition_frame(ConditionFrame::SkipConditions {
remaining: seen_condition_entries + mute_span,
});
let handler_result = self.apply(handler, vec![make_signal_binding_value(&sig)]);
match handler_result {
Ok(_) => {
self.pop_condition_frame();
self.restore_specpdl_roots(specpdl_root_scope);
continue;
}
Err(Flow::Signal(next_sig)) => {
let dispatched =
self.dispatch_signal_if_needed(next_sig).map(|sig| *sig);
self.pop_condition_frame();
self.restore_specpdl_roots(specpdl_root_scope);
return dispatched;
}
Err(flow @ Flow::Throw { .. }) => {
self.pop_condition_frame();
self.restore_specpdl_roots(specpdl_root_scope);
return Err(flow);
}
}
}
}
}
self.maybe_call_debugger_for_signal(&sig, None)?;
sig.search_complete = true;
sig.selected_resume = None;
Ok(sig)
}
fn run_signal_hook(&mut self, sig: &SignalData) -> Result<(), Flow> {
if sig.suppress_signal_hook {
return Ok(());
}
let hook = self
.obarray
.symbol_value("signal-hook-function")
.copied()
.unwrap_or(Value::NIL);
if hook.is_nil() {
return Ok(());
}
self.apply(
hook,
vec![
Value::from_sym_id(sig.symbol),
signal_hook_payload_value(sig),
],
)
.map(|_| ())
}
fn canonicalize_signal_symbol(&self, sig: SignalData) -> SignalData {
let sym_name = sig.symbol_name();
if sym_name == "error" || sym_name == "quit" {
return sig;
}
if self
.obarray
.get_property(sym_name, "error-conditions")
.is_some()
{
return sig;
}
SignalData {
symbol: intern("error"),
data: vec![
Value::string("Invalid error symbol"),
Value::from_sym_id(sig.symbol),
],
raw_data: None,
suppress_signal_hook: sig.suppress_signal_hook,
selected_resume: None,
search_complete: false,
}
}
fn maybe_call_debugger_for_signal(
&mut self,
sig: &SignalData,
matched_clause: Option<&Value>,
) -> Result<(), Flow> {
if self
.obarray
.symbol_value("inhibit-debugger")
.is_some_and(|value| !value.is_nil())
{
return Ok(());
}
let debug_on_signal = self
.obarray
.symbol_value("debug-on-signal")
.is_some_and(|value| !value.is_nil());
let should_consider_debugger = debug_on_signal
|| matched_clause.is_none()
|| matched_clause.is_some_and(condition_value_contains_debug);
if !should_consider_debugger {
return Ok(());
}
let conditions = self.signal_conditions_value(sig);
let debug_setting = if crate::emacs_core::errors::signal_matches_hierarchical(
&self.obarray,
sig.symbol_name(),
"quit",
) {
self.obarray
.symbol_value("debug-on-quit")
.copied()
.unwrap_or(Value::NIL)
} else {
self.obarray
.symbol_value("debug-on-error")
.copied()
.unwrap_or(Value::NIL)
};
if !wants_debugger(&debug_setting, &conditions) {
return Ok(());
}
if self.skip_debugger(sig, &conditions)? {
return Ok(());
}
self.call_debugger_for_signal(sig)
}
fn signal_conditions_value(&self, sig: &SignalData) -> Value {
self.obarray
.get_property(sig.symbol_name(), "error-conditions")
.unwrap_or_else(|| Value::list(vec![Value::from_sym_id(sig.symbol)]))
}
fn skip_debugger(&mut self, sig: &SignalData, conditions: &Value) -> Result<bool, Flow> {
let ignored = self
.obarray
.symbol_value("debug-ignored-errors")
.copied()
.unwrap_or(Value::NIL);
let Some(entries) = list_to_vec(&ignored) else {
return Ok(false);
};
if entries.is_empty() {
return Ok(false);
}
let mut error_message = None;
let error_data = make_signal_binding_value(sig);
let signal_conditions = list_to_vec(conditions).unwrap_or_else(|| vec![*conditions]);
for entry in entries {
if entry.is_string() {
let message = if let Some(message) = error_message {
message
} else {
let rendered = crate::emacs_core::errors::builtin_error_message_string(
self,
vec![error_data],
)?;
error_message = Some(rendered);
rendered
};
if builtins::search::builtin_string_match_p_with_case_fold(
false,
&[entry, message],
)?
.as_fixnum()
.is_some()
{
return Ok(true);
}
continue;
}
if signal_conditions.iter().any(|item| *item == entry) {
return Ok(true);
}
}
Ok(false)
}
fn call_debugger_for_signal(&mut self, sig: &SignalData) -> Result<(), Flow> {
let debugger = self
.obarray
.symbol_value("debugger")
.copied()
.unwrap_or(Value::NIL);
let specpdl_count = self.specpdl.len();
self.specbind(intern("debugger-may-continue"), Value::T);
self.specbind(intern("inhibit-debugger"), Value::T);
let result = self.apply(
debugger,
vec![Value::symbol("error"), make_signal_binding_value(sig)],
);
self.unbind_to(specpdl_count);
result.map(|_| ())
}
fn new_inner(reset_thread_locals: bool) -> Self {
let mut tagged_heap = Box::new(crate::tagged::gc::TaggedHeap::new());
crate::tagged::gc::set_tagged_heap(&mut tagged_heap);
if reset_thread_locals {
super::pdump::runtime::reset_runtime_for_new_heap(
super::pdump::runtime::HeapResetMode::FreshContext,
);
}
let mut obarray = Obarray::new();
let default_directory = std::env::current_dir()
.ok()
.and_then(|p| p.to_str().map(|s| s.to_string()))
.map(|mut s| {
if !s.ends_with('/') {
s.push('/');
}
s
})
.unwrap_or_else(|| "./".to_string());
let completion_in_region_mode_map = make_sparse_list_keymap();
let completion_list_mode_map = make_sparse_list_keymap();
let minibuffer_local_map = make_sparse_list_keymap();
let special_event_map = make_sparse_list_keymap();
let mode_line_window_dedicated_keymap = make_sparse_list_keymap();
let indent_rigidly_map = make_sparse_list_keymap();
let text_mode_map = make_sparse_list_keymap();
let image_slice_map = make_sparse_list_keymap();
let tool_bar_map = make_sparse_list_keymap();
let key_translation_map = make_sparse_list_keymap();
let function_key_map = make_sparse_list_keymap();
let input_decode_map = make_sparse_list_keymap();
let local_function_key_map = make_sparse_list_keymap();
list_keymap_set_parent(local_function_key_map, function_key_map);
list_keymap_define(
special_event_map,
Value::symbol("delete-frame"),
Value::symbol("handle-delete-frame"),
);
list_keymap_define(
special_event_map,
Value::symbol("focus-in"),
Value::symbol("handle-focus-in"),
);
list_keymap_define(
special_event_map,
Value::symbol("focus-out"),
Value::symbol("handle-focus-out"),
);
let standard_syntax_table = super::syntax::builtin_standard_syntax_table(Vec::new())
.expect("startup seeding requires standard syntax table");
let standard_category_table = super::category::ensure_standard_category_table_object()
.expect("startup seeding requires standard category table");
obarray.set_symbol_value("most-positive-fixnum", Value::fixnum(i64::MAX >> 2));
obarray.set_constant("most-positive-fixnum");
obarray.set_symbol_value("most-negative-fixnum", Value::fixnum(-(i64::MAX >> 2) - 1));
obarray.set_constant("most-negative-fixnum");
obarray.set_symbol_value("float-e", Value::make_float(std::f64::consts::E));
obarray.set_symbol_value("float-pi", Value::make_float(std::f64::consts::PI));
obarray.set_symbol_value("pi", Value::make_float(std::f64::consts::PI));
obarray.set_symbol_value("emacs-version", Value::string("31.0.50"));
obarray.set_symbol_value(
"emacs-copyright",
Value::string("Copyright (C) 2026 Free Software Foundation, Inc."),
);
obarray.set_symbol_value("emacs-major-version", Value::fixnum(31));
obarray.set_symbol_value("emacs-minor-version", Value::fixnum(0));
obarray.set_symbol_value("emacs-build-number", Value::fixnum(1));
obarray.set_symbol_value("system-type", Value::symbol("gnu/linux"));
obarray.set_symbol_value("system-uses-terminfo", Value::T);
obarray.set_symbol_value(
"default-directory",
Value::unibyte_string(default_directory.clone()),
);
obarray.set_symbol_value(
"command-line-default-directory",
Value::unibyte_string(default_directory),
);
let obarray_object = Value::vector(vec![Value::NIL]);
obarray.set_symbol_value("obarray", obarray_object);
obarray.set_symbol_value("neovm--obarray-object", obarray_object);
obarray.make_special("obarray");
obarray.set_symbol_value("standard-input", Value::T);
obarray.make_special("standard-input");
obarray.set_symbol_value(
"command-line-args",
Value::list(vec![
Value::string("neovm-worker"),
Value::string("--batch"),
]),
);
obarray.set_symbol_value("command-line-args-left", Value::NIL);
obarray.set_symbol_value("command-line-functions", Value::NIL);
obarray.set_symbol_value("command-line-processed", Value::T);
obarray.set_symbol_value("command-switch-alist", Value::NIL);
obarray.set_symbol_value(
"pdumper-fingerprint",
Value::string(crate::emacs_core::pdump::fingerprint_hex()),
);
obarray.make_special("pdumper-fingerprint");
let exe_path = std::env::current_exe()
.ok()
.and_then(|p| p.canonicalize().ok());
let invocation_name = exe_path
.as_ref()
.and_then(|p| p.file_name())
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "neomacs".to_string());
let invocation_directory = exe_path
.as_ref()
.and_then(|p| p.parent())
.map(|d| format!("{}/", d.to_string_lossy()))
.unwrap_or_else(|| "./".to_string());
obarray.set_symbol_value("invocation-name", Value::string(invocation_name));
obarray.set_symbol_value("invocation-directory", Value::string(invocation_directory));
obarray.set_symbol_value("installation-directory", Value::NIL);
obarray.set_symbol_value("configure-info-directory", Value::NIL);
obarray.set_symbol_value(
"internal--top-level-message",
Value::string("Back to top level"),
);
obarray.set_symbol_value("charset-map-path", Value::NIL);
obarray.set_symbol_value("doc-directory", Value::NIL);
obarray.set_symbol_value("warning-minimum-log-level", Value::keyword(":warning"));
obarray.set_symbol_value("warning-minimum-level", Value::keyword(":warning"));
obarray.set_symbol_value("process-environment", Value::NIL);
obarray.make_special("process-environment");
obarray.set_symbol_value("initial-environment", Value::NIL);
obarray.make_special("initial-environment");
for (name, program) in [
("ctags-program-name", "ctags"),
("etags-program-name", "etags"),
("hexl-program-name", "hexl"),
("emacsclient-program-name", "emacsclient"),
("movemail-program-name", "movemail"),
("ebrowse-program-name", "ebrowse"),
("rcs2log-program-name", "rcs2log"),
] {
obarray.set_symbol_value(name, Value::unibyte_string(program));
obarray.make_special(name);
}
obarray.set_symbol_value("path-separator", Value::string(":"));
obarray.set_symbol_value("shared-game-score-directory", Value::NIL);
obarray.set_symbol_value("system-messages-locale", Value::NIL);
obarray.set_symbol_value("system-time-locale", Value::NIL);
obarray.set_symbol_value("before-init-time", Value::NIL);
obarray.set_symbol_value("after-init-time", Value::NIL);
obarray.set_symbol_value(
"system-configuration",
super::builtins_extra::system_configuration_value(),
);
obarray.set_symbol_value(
"system-configuration-options",
super::builtins_extra::system_configuration_options_value(),
);
obarray.set_symbol_value(
"system-configuration-features",
super::builtins_extra::system_configuration_features_value(),
);
obarray.set_symbol_value("system-name", Value::string("localhost"));
obarray.set_symbol_value("user-full-name", Value::string("unknown"));
obarray.set_symbol_value("user-login-name", Value::string("unknown"));
obarray.set_symbol_value("user-real-login-name", Value::string("unknown"));
obarray.set_symbol_value(
"operating-system-release",
super::builtins_extra::operating_system_release_value(),
);
obarray.set_symbol_value("delayed-warnings-list", Value::NIL);
obarray.set_symbol_value("delayed-warnings-hook", Value::NIL);
obarray.set_symbol_value(
"command-line-ns-option-alist",
Value::list(vec![Value::list(vec![
Value::string("-NSOpen"),
Value::fixnum(1),
Value::symbol("ns-handle-nxopen"),
])]),
);
obarray.set_symbol_value(
"command-line-x-option-alist",
Value::list(vec![Value::list(vec![
Value::string("-display"),
Value::fixnum(1),
Value::symbol("x-handle-display"),
])]),
);
obarray.set_symbol_value("load-path", Value::NIL);
obarray.make_special("load-path");
obarray.set_symbol_value("load-history", Value::NIL);
obarray.set_symbol_value(
"fontset-alias-alist",
super::builtins::symbols::fontset_alias_alist_startup_value(),
);
obarray.set_symbol_value(
"load-suffixes",
Value::list(vec![Value::string(".elc"), Value::string(".el")]),
);
obarray.make_special("load-suffixes");
obarray.set_symbol_value("module-file-suffix", Value::NIL);
obarray.make_special("module-file-suffix");
obarray.set_symbol_value(
"dynamic-library-suffixes",
Value::list(vec![Value::string(std::env::consts::DLL_SUFFIX)]),
);
obarray.make_special("dynamic-library-suffixes");
obarray.set_symbol_value(
"load-file-rep-suffixes",
Value::list(vec![Value::string("")]),
);
obarray.make_special("load-file-rep-suffixes");
obarray.set_symbol_value("file-coding-system-alist", Value::NIL);
obarray.set_symbol_value("features", Value::NIL);
obarray.set_symbol_value_id(lexical_binding_symbol(), Value::NIL);
obarray.set_symbol_value("load-prefer-newer", Value::NIL);
obarray.set_symbol_value("load-file-name", Value::NIL);
obarray.make_special("load-file-name");
obarray.set_symbol_value("noninteractive", Value::T);
obarray.set_symbol_value("inhibit-quit", Value::NIL);
obarray.set_symbol_value("symbols-with-pos-enabled", Value::NIL);
obarray.make_special("symbols-with-pos-enabled");
obarray.set_symbol_value("print-symbols-bare", Value::NIL);
obarray.make_special("print-symbols-bare");
for name in [
"print-length",
"print-level",
"print-circle",
"print-quoted",
"print-escape-newlines",
"print-escape-control-characters",
"print-escape-nonascii",
"print-escape-multibyte",
"print-gensym",
"print-continuous-numbering",
"print-number-table",
"print-charset-text-property",
"print-integers-as-characters",
"print-unreadable-function",
] {
obarray.set_symbol_value(name, Value::NIL);
obarray.make_special(name);
}
obarray.set_symbol_value("print-quoted", Value::T);
obarray.set_symbol_value("text-quoting-style", Value::NIL);
obarray.set_symbol_value("char-code-property-alist", Value::NIL);
obarray.set_symbol_value("redisplay--inhibit-bidi", Value::NIL);
obarray.set_symbol_value("resize-mini-windows", Value::NIL);
for name in [
"alter-fullscreen-frames",
"auto-save-no-message",
"auto-save-visited-file-name",
"blink-cursor-alist",
"composition-break-at-point",
"debug-on-quit",
"debugger-stack-frame-as-list",
"default-frame-alist",
"delete-by-moving-to-trash",
"display-fill-column-indicator",
"display-fill-column-indicator-character",
"display-line-numbers",
"display-line-numbers-widen",
"display-line-numbers-width",
"display-raw-bytes-as-hex",
"echo-keystrokes-help",
"enable-character-translation",
"enable-recursive-minibuffers",
"fast-but-imprecise-scrolling",
"focus-follows-mouse",
"font-use-system-font",
"frame-resize-pixelwise",
"garbage-collection-messages",
"highlight-nonselected-windows",
"history-delete-duplicates",
"inhibit-eol-conversion",
"inverse-video",
"kill-buffer-delete-auto-save-files",
"line-number-display-limit",
"make-pointer-invisible",
"menu-bar-mode",
"minibuffer-auto-raise",
"mode-line-compact",
"mouse-autoselect-window",
"mouse-prefer-closest-glyph",
"no-redraw-on-reenter",
"parse-sexp-ignore-comments",
"read-buffer-completion-ignore-case",
"record-all-keys",
"resize-mini-frames",
"ring-bell-function",
"scalable-fonts-allowed",
"scroll-preserve-screen-position",
"show-trailing-whitespace",
"tab-bar-mode",
"tab-bar-position",
"temp-buffer-show-function",
"tool-bar-mode",
"tool-bar-style",
"tooltip-reuse-hidden-frame",
"treesit-extra-load-path",
"treesit-auto-install-grammar",
"treesit-enabled-modes",
"treesit-language-remap-alist",
"treesit-language-source-alist",
"treesit-load-name-override-list",
"treesit-languages-require-line-column-tracking",
"treesit-major-mode-remap-alist",
"treesit-thing-settings",
"undo-outer-limit",
"unibyte-display-via-language-environment",
"use-short-answers",
"visible-bell",
"window-combination-resize",
"window-resize-pixelwise",
"word-wrap-by-category",
"words-include-escapes",
"x-dnd-disable-motif-drag",
"x-gtk-show-hidden-files",
"x-gtk-use-native-input",
"x-gtk-use-old-file-dialog",
"x-stretch-cursor",
"x-underline-at-descent-line",
"x-use-underline-position-properties",
"x-pointer-shape",
"x-nontext-pointer-shape",
"x-mode-pointer-shape",
"x-sensitive-text-pointer-shape",
"x-hourglass-pointer-shape",
"x-window-horizontal-drag-cursor",
"x-window-vertical-drag-cursor",
"x-window-left-edge-cursor",
"x-window-top-left-corner-cursor",
"x-window-top-edge-cursor",
"x-window-top-right-corner-cursor",
"x-window-right-edge-cursor",
"x-window-bottom-right-corner-cursor",
"x-window-bottom-edge-cursor",
"x-window-bottom-left-corner-cursor",
"x-cursor-fore-pixel",
] {
obarray.set_symbol_value(name, Value::NIL);
}
obarray.set_symbol_value("menu-bar-mode", Value::T);
obarray.set_symbol_value("tool-bar-mode", Value::T);
for name in [
"auto-hscroll-mode",
"create-lockfiles",
"delete-auto-save-files",
"delete-exited-processes",
"display-fill-column-indicator-column",
"display-hourglass",
"display-line-numbers-current-absolute",
"make-cursor-line-fully-visible",
"menu-prompting",
"mode-line-in-non-selected-windows",
"mouse-highlight",
"open-paren-in-column-0-is-defun-start",
"overflow-newline-into-fringe",
"read-minibuffer-restore-windows",
"scroll-bar-adjust-thumb-portion",
"select-active-regions",
"translate-upper-case-key-bindings",
"use-dialog-box",
"use-file-dialog",
"use-system-tooltips",
"visible-cursor",
"x-gtk-file-dialog-help-text",
"x-select-enable-clipboard-manager",
] {
obarray.set_symbol_value(name, Value::T);
}
obarray.set_symbol_value("auto-save-interval", Value::fixnum(300));
obarray.set_symbol_value("auto-save-timeout", Value::fixnum(30));
obarray.set_symbol_value("display-line-numbers-major-tick", Value::fixnum(0));
obarray.set_symbol_value("display-line-numbers-minor-tick", Value::fixnum(0));
obarray.set_symbol_value("double-click-fuzz", Value::fixnum(3));
obarray.set_symbol_value("double-click-time", Value::fixnum(500));
obarray.set_symbol_value("echo-keystrokes", Value::fixnum(1));
obarray.set_symbol_value("gc-cons-threshold", Value::fixnum(8_000_000));
obarray.set_symbol_value("help-char", Value::fixnum(8));
obarray.set_symbol_value("hourglass-delay", Value::fixnum(1));
obarray.set_symbol_value("hscroll-margin", Value::fixnum(5));
obarray.set_symbol_value("hscroll-step", Value::fixnum(0));
obarray.set_symbol_value("line-number-display-limit-width", Value::fixnum(200));
obarray.set_symbol_value("maximum-scroll-margin", Value::fixnum(25));
obarray.set_symbol_value("message-log-max", Value::fixnum(1000));
obarray.set_symbol_value("meta-prefix-char", Value::fixnum(27));
obarray.set_symbol_value("next-screen-context-lines", Value::fixnum(2));
obarray.set_symbol_value("overline-margin", Value::fixnum(2));
obarray.set_symbol_value("polling-period", Value::fixnum(2));
obarray.set_symbol_value("process-error-pause-time", Value::fixnum(1));
obarray.set_symbol_value("scroll-conservatively", Value::fixnum(0));
obarray.set_symbol_value("scroll-margin", Value::fixnum(0));
obarray.set_symbol_value("scroll-step", Value::fixnum(0));
obarray.set_symbol_value("tool-bar-max-label-size", Value::fixnum(10));
obarray.set_symbol_value("truncate-partial-width-windows", Value::fixnum(50));
obarray.set_symbol_value("underline-minimum-offset", Value::fixnum(1));
obarray.set_symbol_value("undo-limit", Value::fixnum(160000));
obarray.set_symbol_value("undo-strong-limit", Value::fixnum(240000));
obarray.set_symbol_value("eol-mnemonic-dos", Value::string("\\"));
obarray.set_symbol_value("eol-mnemonic-mac", Value::string("/"));
obarray.set_symbol_value("eol-mnemonic-undecided", Value::string(":"));
obarray.set_symbol_value("eol-mnemonic-unix", Value::string(":"));
obarray.set_symbol_value(
"report-emacs-bug-address",
Value::string("bug-gnu-emacs@gnu.org"),
);
obarray.set_symbol_value("yes-or-no-prompt", Value::string("(yes or no) "));
obarray.set_symbol_value("gc-cons-percentage", Value::make_float(0.1));
obarray.set_symbol_value("max-mini-window-height", Value::make_float(0.25));
obarray.set_symbol_value("image-scaling-factor", Value::make_float(1.0));
obarray.set_symbol_value("global-mode-string", Value::NIL);
obarray.set_symbol_value("load-in-progress", Value::NIL);
obarray.set_symbol_value("internal--daemon-sockname", Value::NIL);
obarray.set_symbol_value("byte-compile-warnings", Value::T);
obarray.set_symbol_value("history-length", Value::fixnum(100));
obarray.set_symbol_value("minibuffer-follows-selected-frame", Value::T);
obarray.set_symbol_value("recenter-redisplay", Value::symbol("tty"));
obarray.set_symbol_value("iconify-child-frame", Value::symbol("iconify-top-level"));
obarray.set_symbol_value("frame-inhibit-implied-resize", Value::NIL);
obarray.set_symbol_value("mark-even-if-inactive", Value::T);
obarray.set_symbol_value("read-buffer-function", Value::NIL);
obarray.set_symbol_value("minibuffer-prompt-properties", Value::NIL);
obarray.set_symbol_value("help-event-list", Value::NIL);
obarray.set_symbol_value(
"prefix-help-command",
Value::symbol("describe-prefix-bindings"),
);
obarray.set_symbol_value("debug-ignored-errors", Value::NIL);
obarray.set_symbol_value("debug-on-event", Value::NIL);
obarray.set_symbol_value("debug-on-signal", Value::NIL);
for name in [
"imagemagick-render-type",
"window-combination-limit",
"void-text-area-pointer",
"x-bitmap-file-path",
"x-gtk-use-system-tooltips",
"x-scroll-event-delta-factor",
"x-auto-preserve-selections",
"xwidget-internal",
"temporary-file-directory",
"vertical-centering-font-regexp",
"ns-control-modifier",
"ns-right-control-modifier",
"ns-command-modifier",
"ns-right-command-modifier",
"ns-alternate-modifier",
"ns-right-alternate-modifier",
"ns-function-modifier",
"ns-antialias-text",
"ns-auto-hide-menu-bar",
"ns-confirm-quit",
"ns-use-native-fullscreen",
"ns-use-fullscreen-animation",
"ns-use-srgb-colorspace",
"ns-scroll-event-delta-factor",
"ns-click-through",
"w32-follow-system-dark-mode",
"dos-display-scancodes",
"dos-hyper-key",
"dos-super-key",
"dos-keypad-mode",
"dos-unsupported-char-glyph",
"haiku-debug-on-fatal-error",
"haiku-use-system-tooltips",
"xwidget-webkit-disable-javascript",
] {
obarray.set_symbol_value(name, Value::NIL);
}
obarray.set_symbol_value("values", Value::NIL);
obarray.set_symbol_value("eval-buffer-list", Value::NIL);
obarray.make_special("eval-buffer-list");
obarray.set_symbol_value("lread--unescaped-character-literals", Value::NIL);
obarray.make_special("lread--unescaped-character-literals");
obarray.set_symbol_value("load-read-function", Value::symbol("read"));
obarray.make_special("load-read-function");
obarray.set_symbol_value("load-source-file-function", Value::NIL);
obarray.make_special("load-source-file-function");
obarray.set_symbol_value("load-true-file-name", Value::NIL);
obarray.make_special("load-true-file-name");
obarray.set_symbol_value("user-init-file", Value::NIL);
obarray.make_special("user-init-file");
obarray.set_symbol_value("source-directory", Value::NIL);
obarray.make_special("source-directory");
obarray.set_symbol_value("after-load-alist", Value::NIL);
obarray.make_special("after-load-alist");
obarray.set_symbol_value("load-history", Value::NIL);
obarray.make_special("load-history");
obarray.set_symbol_value("current-load-list", Value::NIL);
obarray.make_special("current-load-list");
obarray.set_symbol_value("preloaded-file-list", Value::NIL);
obarray.make_special("preloaded-file-list");
obarray.set_symbol_value("byte-boolean-vars", Value::NIL);
obarray.make_special("byte-boolean-vars");
obarray.set_symbol_value(
"bytecomp-version-regexp",
Value::string(r#"^;;;.\(in Emacs version\|bytecomp version FSF\)"#),
);
obarray.make_special("bytecomp-version-regexp");
obarray.set_symbol_value("load-path-filter-function", Value::NIL);
obarray.make_special("load-path-filter-function");
obarray.set_symbol_value("internal--get-default-lexical-binding-function", Value::NIL);
obarray.make_special("internal--get-default-lexical-binding-function");
obarray.set_symbol_value("read-symbol-shorthands", Value::NIL);
obarray.make_special("read-symbol-shorthands");
obarray.set_symbol_value("macroexp--dynvars", Value::NIL);
obarray.make_special("macroexp--dynvars");
let core_eval_symbols = install_core_eval_symbols(&mut obarray, true);
obarray.set_symbol_value("inhibit-debugger", Value::NIL);
obarray.make_special("inhibit-debugger");
obarray.set_symbol_value("debug-on-error", Value::NIL);
obarray.make_special("debug-on-error");
obarray.set_symbol_value("debug-on-quit", Value::NIL);
obarray.make_special("debug-on-quit");
obarray.set_symbol_value("debug-on-signal", Value::NIL);
obarray.make_special("debug-on-signal");
obarray.set_symbol_value("debug-ignored-errors", Value::NIL);
obarray.make_special("debug-ignored-errors");
obarray.set_symbol_value("debugger-may-continue", Value::NIL);
obarray.make_special("debugger-may-continue");
obarray.set_symbol_value("internal-when-entered-debugger", Value::fixnum(-1));
obarray.make_special("internal-when-entered-debugger");
obarray.set_symbol_value("signal-hook-function", Value::NIL);
obarray.make_special("signal-hook-function");
obarray.set_symbol_value("internal-make-interpreted-closure-function", Value::NIL);
obarray.make_special("internal-make-interpreted-closure-function");
obarray.set_symbol_value("debugger", Value::symbol("debug-early"));
obarray.make_special("debugger");
obarray.set_symbol_value("standard-output", Value::T);
obarray.set_symbol_value("baud-rate", Value::fixnum(38400));
obarray.set_symbol_value("search-slow-speed", Value::fixnum(1200));
obarray.set_symbol_value("init-file-debug", Value::NIL);
let exec_path: Vec<Value> = std::env::var("PATH")
.unwrap_or_default()
.split(':')
.map(|s| Value::unibyte_string(s.to_string()))
.collect();
obarray.set_symbol_value("exec-path", Value::list(exec_path));
obarray.set_symbol_value(
"exec-directory",
Value::unibyte_string(
std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|d| d.to_string_lossy().to_string()))
.unwrap_or_else(|| "/usr/bin/".to_string()),
),
);
obarray.set_symbol_value(
"exec-suffixes",
Value::list(vec![Value::unibyte_string("")]),
);
obarray.set_symbol_value("buffer-read-only", Value::NIL);
obarray.set_symbol_value("left-margin-width", Value::NIL);
obarray.set_symbol_value("right-margin-width", Value::NIL);
obarray.set_symbol_value("left-fringe-width", Value::NIL);
obarray.set_symbol_value("right-fringe-width", Value::NIL);
obarray.set_symbol_value("fringes-outside-margins", Value::NIL);
obarray.set_symbol_value("scroll-bar-width", Value::NIL);
obarray.set_symbol_value("scroll-bar-height", Value::NIL);
obarray.set_symbol_value("vertical-scroll-bar", Value::T);
obarray.set_symbol_value("horizontal-scroll-bar", Value::T);
obarray.set_symbol_value("kill-ring", Value::NIL);
obarray.set_symbol_value("kill-ring-yank-pointer", Value::NIL);
obarray.set_symbol_value("last-command", Value::NIL);
obarray.set_symbol_value("current-fill-column--has-warned", Value::NIL);
obarray.set_symbol_value("current-input-method", Value::NIL);
obarray.set_symbol_value("current-input-method-title", Value::NIL);
obarray.set_symbol_value("current-iso639-language", Value::NIL);
obarray.set_symbol_value("current-key-remap-sequence", Value::NIL);
obarray.set_symbol_value("current-language-environment", Value::string("UTF-8"));
obarray.set_symbol_value(
"current-load-list",
Value::list(vec![
Value::symbol("comp--no-native-compile"),
Value::cons(
Value::symbol("defun"),
Value::symbol("load--fixup-all-elns"),
),
Value::symbol("load--eln-dest-dir"),
Value::symbol("load--bin-dest-dir"),
]),
);
obarray.set_symbol_value("current-locale-environment", Value::string("C.UTF-8"));
obarray.set_symbol_value("current-minibuffer-command", Value::NIL);
obarray.set_symbol_value("current-time-list", Value::T);
obarray.set_symbol_value("current-transient-input-method", Value::NIL);
obarray.set_symbol_value("real-last-command", Value::NIL);
obarray.set_symbol_value("last-repeatable-command", Value::NIL);
obarray.set_symbol_value("this-original-command", Value::NIL);
obarray.set_symbol_value("prefix-arg", Value::NIL);
obarray.set_symbol_value("defining-kbd-macro", Value::NIL);
obarray.set_symbol_value("executing-kbd-macro", Value::NIL);
obarray.set_symbol_value("executing-kbd-macro-index", Value::fixnum(0));
obarray.set_symbol_value("kbd-macro-termination-hook", Value::NIL);
obarray.set_symbol_value("command-history", Value::NIL);
obarray.set_symbol_value("extended-command-history", Value::NIL);
obarray.set_symbol_value("completion-ignore-case", Value::NIL);
obarray.set_symbol_value("read-buffer-completion-ignore-case", Value::NIL);
obarray.set_symbol_value("read-file-name-completion-ignore-case", Value::NIL);
obarray.set_symbol_value("completion-regexp-list", Value::NIL);
obarray.set_symbol_value("completion--all-sorted-completions-location", Value::NIL);
obarray.set_symbol_value("completion--capf-misbehave-funs", Value::NIL);
obarray.set_symbol_value("completion--capf-safe-funs", Value::NIL);
obarray.set_symbol_value(
"completion--embedded-envvar-re",
Value::string(
"\\(?:^\\|[^$]\\(?:\\$\\$\\)*\\)\\$\\([[:alnum:]_]*\\|{\\([^}]*\\)\\)\\'",
),
);
obarray.set_symbol_value("completion--flex-score-last-md", Value::NIL);
obarray.set_symbol_value("completion-all-sorted-completions", Value::NIL);
obarray.set_symbol_value(
"completion--cycling-threshold-type",
Value::list(vec![Value::symbol("choice")]),
);
obarray.set_symbol_value(
"completion--styles-type",
Value::list(vec![Value::symbol("repeat")]),
);
obarray.set_symbol_value(
"completion-at-point-functions",
Value::list(vec![Value::symbol("tags-completion-at-point-function")]),
);
obarray.set_symbol_value(
"completion-setup-hook",
Value::list(vec![Value::symbol("completion-setup-function")]),
);
obarray.set_symbol_value(
"completion-in-region-mode-map",
completion_in_region_mode_map,
);
obarray.set_symbol_value("completion-list-mode-map", completion_list_mode_map);
obarray.set_symbol_value("completion-list-mode-syntax-table", standard_syntax_table);
obarray.set_symbol_value(
"completion-list-mode-abbrev-table",
Value::symbol("completion-list-mode-abbrev-table"),
);
obarray.set_symbol_value("completion-list-mode-hook", Value::NIL);
obarray.set_symbol_value(
"completion-ignored-extensions",
Value::list(vec![
Value::string(".o"),
Value::string("~"),
Value::string(".elc"),
]),
);
obarray.set_symbol_value(
"completion-styles",
Value::list(vec![
Value::symbol("basic"),
Value::symbol("partial-completion"),
Value::symbol("emacs22"),
]),
);
obarray.set_symbol_value(
"completion-category-defaults",
Value::list(vec![
Value::list(vec![
Value::symbol("buffer"),
Value::list(vec![
Value::symbol("styles"),
Value::symbol("basic"),
Value::symbol("substring"),
]),
]),
Value::list(vec![
Value::symbol("unicode-name"),
Value::list(vec![
Value::symbol("styles"),
Value::symbol("basic"),
Value::symbol("substring"),
]),
]),
Value::list(vec![
Value::symbol("project-file"),
Value::list(vec![Value::symbol("styles"), Value::symbol("substring")]),
]),
Value::list(vec![
Value::symbol("xref-location"),
Value::list(vec![Value::symbol("styles"), Value::symbol("substring")]),
]),
Value::list(vec![
Value::symbol("info-menu"),
Value::list(vec![
Value::symbol("styles"),
Value::symbol("basic"),
Value::symbol("substring"),
]),
]),
Value::list(vec![
Value::symbol("symbol-help"),
Value::list(vec![
Value::symbol("styles"),
Value::symbol("basic"),
Value::symbol("shorthand"),
Value::symbol("substring"),
]),
]),
Value::list(vec![
Value::symbol("calendar-month"),
Value::cons(
Value::symbol("display-sort-function"),
Value::symbol("identity"),
),
]),
]),
);
obarray.set_symbol_value("completion-category-overrides", Value::NIL);
obarray.set_symbol_value("completion-cycle-threshold", Value::NIL);
obarray.set_symbol_value("completions-detailed", Value::NIL);
obarray.set_symbol_value("completions-format", Value::symbol("horizontal"));
obarray.set_symbol_value("completions-group", Value::NIL);
obarray.set_symbol_value("completions-group-format", Value::string(" %s "));
obarray.set_symbol_value("completions-group-sort", Value::NIL);
obarray.set_symbol_value(
"completions-header-format",
Value::string("%s possible completions:\n"),
);
obarray.set_symbol_value(
"completions-highlight-face",
Value::symbol("completions-highlight"),
);
obarray.set_symbol_value("completions-max-height", Value::NIL);
obarray.set_symbol_value("completions-sort", Value::symbol("alphabetical"));
obarray.set_symbol_value("completion-auto-help", Value::T);
obarray.set_symbol_value("completion-auto-deselect", Value::T);
obarray.set_symbol_value("completion-auto-select", Value::NIL);
obarray.set_symbol_value("completion-auto-wrap", Value::T);
obarray.set_symbol_value("completion-base-position", Value::NIL);
obarray.set_symbol_value("completion-cycling", Value::NIL);
obarray.set_symbol_value("completion-extra-properties", Value::NIL);
obarray.set_symbol_value("completion-fail-discreetly", Value::NIL);
obarray.set_symbol_value("completion-flex-nospace", Value::NIL);
obarray.set_symbol_value("completion-in-region--data", Value::NIL);
obarray.set_symbol_value(
"completion-in-region-function",
Value::symbol("completion--in-region"),
);
obarray.set_symbol_value("completion-in-region-functions", Value::NIL);
obarray.set_symbol_value("completion-in-region-mode", Value::NIL);
obarray.set_symbol_value("completion-in-region-mode--predicate", Value::NIL);
obarray.set_symbol_value("completion-in-region-mode-hook", Value::NIL);
obarray.set_symbol_value("completion-in-region-mode-predicate", Value::NIL);
obarray.set_symbol_value("completion-show-help", Value::T);
obarray.set_symbol_value("completion-show-inline-help", Value::T);
obarray.set_symbol_value("completion-lazy-hilit", Value::NIL);
obarray.set_symbol_value("completion-lazy-hilit-fn", Value::NIL);
obarray.set_symbol_value(
"completion-list-insert-choice-function",
Value::symbol("completion--replace"),
);
obarray.set_symbol_value("completion-no-auto-exit", Value::NIL);
obarray.set_symbol_value(
"completion-pcm--delim-wild-regex",
Value::string("[-_./:| *]"),
);
obarray.set_symbol_value("completion-pcm--regexp", Value::NIL);
obarray.set_symbol_value(
"completion-pcm-complete-word-inserts-delimiters",
Value::NIL,
);
obarray.set_symbol_value("completion-pcm-word-delimiters", Value::string("-_./:| "));
obarray.set_symbol_value("completion-reference-buffer", Value::NIL);
obarray.set_symbol_value("completion-tab-width", Value::NIL);
obarray.set_symbol_value("enable-recursive-minibuffers", Value::NIL);
obarray.set_symbol_value("history-length", Value::fixnum(100));
obarray.set_symbol_value("history-delete-duplicates", Value::NIL);
obarray.set_symbol_value("history-add-new-input", Value::T);
obarray.set_symbol_value("read-buffer-function", Value::NIL);
obarray.set_symbol_value(
"read-file-name-function",
Value::symbol("read-file-name-default"),
);
obarray.set_symbol_value("read-expression-history", Value::NIL);
obarray.set_symbol_value("read-number-history", Value::NIL);
obarray.set_symbol_value("read-char-history", Value::NIL);
obarray.set_symbol_value("read-answer-short", Value::symbol("auto"));
obarray.set_symbol_value("read-char-by-name-sort", Value::NIL);
obarray.set_symbol_value("read-char-choice-use-read-key", Value::NIL);
obarray.set_symbol_value("read-circle", Value::T);
obarray.make_special("read-circle");
obarray.set_symbol_value("read-envvar-name-history", Value::NIL);
obarray.set_symbol_value("read-face-name-sample-text", Value::string("SAMPLE"));
obarray.set_symbol_value("read-key-delay", Value::make_float(0.01));
obarray.set_symbol_value(
"read-answer-map--memoize",
Value::hash_table(HashTableTest::Equal),
);
obarray.set_symbol_value("read-extended-command-mode", Value::NIL);
obarray.set_symbol_value("read-extended-command-mode-hook", Value::NIL);
obarray.set_symbol_value("read-extended-command-predicate", Value::NIL);
obarray.set_symbol_value("read-hide-char", Value::NIL);
obarray.set_symbol_value("read-mail-command", Value::symbol("rmail"));
obarray.set_symbol_value("read-minibuffer-restore-windows", Value::T);
obarray.set_symbol_value("read-only-mode-hook", Value::NIL);
obarray.set_symbol_value("read-process-output-max", Value::fixnum(65536));
obarray.set_symbol_value("read-quoted-char-radix", Value::fixnum(8));
obarray.set_symbol_value("read-regexp--case-fold", Value::NIL);
obarray.set_symbol_value("read-regexp-defaults-function", Value::NIL);
obarray.set_symbol_value("read-symbol-shorthands", Value::NIL);
obarray.set_symbol_value(
"minibuffer-frame-alist",
Value::list(vec![
Value::cons(Value::symbol("width"), Value::fixnum(80)),
Value::cons(Value::symbol("height"), Value::fixnum(2)),
]),
);
obarray.set_symbol_value(
"minibuffer-inactive-mode-abbrev-table",
Value::symbol("minibuffer-inactive-mode-abbrev-table"),
);
obarray.set_symbol_value("minibuffer-inactive-mode-hook", Value::NIL);
obarray.set_symbol_value(
"minibuffer-inactive-mode-syntax-table",
standard_syntax_table,
);
obarray.set_symbol_value(
"minibuffer-mode-abbrev-table",
Value::symbol("minibuffer-mode-abbrev-table"),
);
obarray.set_symbol_value("minibuffer-mode-hook", Value::NIL);
obarray.set_symbol_value("minibuffer-local-map", minibuffer_local_map);
obarray.set_symbol_value("minibuffer-local-filename-syntax", standard_syntax_table);
obarray.set_symbol_value("minibuffer-history", Value::NIL);
obarray.set_symbol_value(
"minibuffer-history-variable",
Value::symbol("minibuffer-history"),
);
obarray.set_symbol_value("minibuffer-history-position", Value::NIL);
obarray.set_symbol_value("minibuffer-history-isearch-message-overlay", Value::NIL);
obarray.set_symbol_value("minibuffer-history-search-history", Value::NIL);
obarray.set_symbol_value("minibuffer-history-sexp-flag", Value::NIL);
obarray.set_symbol_value("minibuffer-default", Value::NIL);
obarray.set_symbol_value("minibuffer-default-add-done", Value::NIL);
obarray.set_symbol_value(
"minibuffer-default-add-function",
Value::symbol("minibuffer-default-add-completions"),
);
obarray.set_symbol_value("minibuffer--original-buffer", Value::NIL);
obarray.set_symbol_value("minibuffer--regexp-primed", Value::NIL);
obarray.set_symbol_value(
"minibuffer--regexp-prompt-regexp",
Value::string(
"\\(?:Posix search\\|RE search\\|Search for regexp\\|Query replace regexp\\)",
),
);
obarray.set_symbol_value("minibuffer--require-match", Value::NIL);
obarray.set_symbol_value("minibuffer-auto-raise", Value::NIL);
obarray.set_symbol_value("minibuffer-follows-selected-frame", Value::T);
obarray.set_symbol_value(
"minibuffer-exit-hook",
Value::list(vec![
Value::symbol("minibuffer--regexp-exit"),
Value::symbol("minibuffer-exit-on-screen-keyboard"),
Value::symbol("minibuffer-restore-windows"),
]),
);
obarray.set_symbol_value("minibuffer-completion-table", Value::NIL);
obarray.set_symbol_value("minibuffer-completion-predicate", Value::NIL);
obarray.set_symbol_value("minibuffer-completion-confirm", Value::NIL);
obarray.set_symbol_value("minibuffer-completion-auto-choose", Value::T);
obarray.set_symbol_value("minibuffer-completion-base", Value::NIL);
obarray.set_symbol_value("minibuffer-help-form", Value::NIL);
obarray.set_symbol_value("minibuffer-completing-file-name", Value::NIL);
obarray.set_symbol_value("minibuffer-regexp-mode", Value::T);
obarray.set_symbol_value("minibuffer-regexp-mode-hook", Value::NIL);
obarray.set_symbol_value(
"minibuffer-regexp-prompts",
Value::list(vec![
Value::string("Posix search"),
Value::string("RE search"),
Value::string("Search for regexp"),
Value::string("Query replace regexp"),
]),
);
obarray.set_symbol_value("minibuffer-message-clear-timeout", Value::NIL);
obarray.set_symbol_value("minibuffer-message-overlay", Value::NIL);
obarray.set_symbol_value("minibuffer-message-properties", Value::NIL);
obarray.set_symbol_value("minibuffer-message-timeout", Value::fixnum(2));
obarray.set_symbol_value("minibuffer-message-timer", Value::NIL);
obarray.set_symbol_value("minibuffer-lazy-count-format", Value::string("%s "));
obarray.set_symbol_value("minibuffer-text-before-history", Value::NIL);
obarray.set_symbol_value(
"minibuffer-prompt-properties",
Value::list(vec![
Value::symbol("read-only"),
Value::T,
Value::symbol("face"),
Value::symbol("minibuffer-prompt"),
]),
);
obarray.set_symbol_value("minibuffer-allow-text-properties", Value::NIL);
obarray.set_symbol_value("minibuffer-scroll-window", Value::NIL);
obarray.make_special("minibuffer-scroll-window");
obarray.set_symbol_value("other-window-scroll-buffer", Value::NIL);
obarray.make_special("other-window-scroll-buffer");
obarray.set_symbol_value("other-window-scroll-default", Value::NIL);
obarray.make_special("other-window-scroll-default");
obarray.set_symbol_value("minibuffer-visible-completions", Value::NIL);
obarray.set_symbol_value("minibuffer-visible-completions--always-bind", Value::NIL);
obarray.set_symbol_value("minibuffer-depth-indicate-mode", Value::NIL);
obarray.set_symbol_value(
"minibuffer-default-prompt-format",
Value::string(" (default %s)"),
);
obarray.set_symbol_value("minibuffer-beginning-of-buffer-movement", Value::NIL);
obarray.set_symbol_value("minibuffer-electric-default-mode", Value::NIL);
obarray.set_symbol_value("minibuffer-temporary-goal-position", Value::NIL);
obarray.set_symbol_value(
"minibuffer-confirm-exit-commands",
Value::list(vec![
Value::symbol("completion-at-point"),
Value::symbol("minibuffer-complete"),
Value::symbol("minibuffer-complete-word"),
]),
);
obarray.set_symbol_value("minibuffer-history-case-insensitive-variables", Value::NIL);
obarray.set_symbol_value("minibuffer-on-screen-keyboard-displayed", Value::NIL);
obarray.set_symbol_value("minibuffer-on-screen-keyboard-timer", Value::NIL);
obarray.set_symbol_value(
"minibuffer-setup-hook",
Value::list(vec![
Value::symbol("rfn-eshadow-setup-minibuffer"),
Value::symbol("minibuffer--regexp-setup"),
Value::symbol("minibuffer-setup-on-screen-keyboard"),
Value::symbol("minibuffer-error-initialize"),
Value::symbol("minibuffer-history-isearch-setup"),
Value::symbol("minibuffer-history-initialize"),
]),
);
obarray.set_symbol_value("regexp-search-ring", Value::NIL);
obarray.set_symbol_value("regexp-search-ring-max", Value::fixnum(16));
obarray.set_symbol_value("regexp-search-ring-yank-pointer", Value::NIL);
obarray.set_symbol_value("search-ring", Value::NIL);
obarray.set_symbol_value("search-ring-max", Value::fixnum(16));
obarray.set_symbol_value("search-ring-update", Value::NIL);
obarray.set_symbol_value("search-ring-yank-pointer", Value::NIL);
obarray.set_symbol_value("last-abbrev", Value::NIL);
obarray.set_symbol_value("last-abbrev-location", Value::fixnum(0));
obarray.set_symbol_value("last-abbrev-text", Value::NIL);
obarray.set_symbol_value("last-command-event", Value::NIL);
obarray.set_symbol_value("last-event-device", Value::NIL);
obarray.set_symbol_value("last-input-event", Value::NIL);
obarray.set_symbol_value("last-nonmenu-event", Value::NIL);
obarray.set_symbol_value("last-prefix-arg", Value::NIL);
obarray.set_symbol_value("last-kbd-macro", Value::NIL);
obarray.set_symbol_value("last-code-conversion-error", Value::NIL);
obarray.set_symbol_value("last-coding-system-specified", Value::NIL);
obarray.set_symbol_value("last-coding-system-used", Value::symbol("undecided-unix"));
obarray.set_symbol_value("last-next-selection-coding-system", Value::NIL);
obarray.set_symbol_value("command-debug-status", Value::NIL);
obarray.set_symbol_value(
"command-error-function",
Value::symbol("help-command-error-confusable-suggestions"),
);
obarray.set_symbol_value("key-substitution-in-progress", Value::NIL);
obarray.set_symbol_value("this-command", Value::NIL);
obarray.set_symbol_value("real-this-command", Value::NIL);
obarray.set_symbol_value("this-command-keys-shift-translated", Value::NIL);
obarray.set_symbol_value("current-prefix-arg", Value::NIL);
obarray.set_symbol_value("track-mouse", Value::NIL);
obarray.make_special("track-mouse");
obarray.set_symbol_value(
"while-no-input-ignore-events",
Value::list(vec![
Value::symbol("thread-event"),
Value::symbol("file-notify"),
Value::symbol("dbus-event"),
Value::symbol("select-window"),
Value::symbol("help-echo"),
Value::symbol("move-frame"),
Value::symbol("iconify-frame"),
Value::symbol("make-frame-visible"),
Value::symbol("focus-in"),
Value::symbol("focus-out"),
Value::symbol("config-changed-event"),
Value::symbol("selection-request"),
Value::symbol("monitors-changed"),
]),
);
obarray.make_special("while-no-input-ignore-events");
obarray.set_symbol_value("input-pending-p-filter-events", Value::T);
obarray.make_special("input-pending-p-filter-events");
obarray.set_symbol_value("deactivate-mark", Value::T);
obarray.set_symbol_value("mark-active", Value::NIL);
obarray.set_symbol_value("mark-even-if-inactive", Value::T);
obarray.set_symbol_value("mark-ring", Value::NIL);
obarray.set_symbol_value("mark-ring-max", Value::fixnum(16));
obarray.set_symbol_value("transient-mark-mode", Value::NIL);
obarray.set_symbol_value("transient-mark-mode-hook", Value::NIL);
obarray.set_symbol_value("post-select-region-hook", Value::NIL);
obarray.set_symbol_value("echo-area-clear-hook", Value::NIL);
obarray.set_symbol_value("display-monitors-changed-functions", Value::NIL);
obarray.set_symbol_value("delete-terminal-functions", Value::NIL);
obarray.set_symbol_value("suspend-tty-functions", Value::NIL);
obarray.set_symbol_value("resume-tty-functions", Value::NIL);
obarray.set_symbol_value("overriding-local-map", Value::NIL);
obarray.make_special("overriding-local-map");
obarray.set_symbol_value("overriding-local-map-menu-flag", Value::NIL);
obarray.make_special("overriding-local-map-menu-flag");
obarray.set_symbol_value("overriding-plist-environment", Value::NIL);
obarray.set_symbol_value("overriding-terminal-local-map", Value::NIL);
obarray.make_special("overriding-terminal-local-map");
obarray.set_symbol_value("overriding-text-conversion-style", Value::symbol("lambda"));
obarray.set_symbol_value("special-event-map", special_event_map);
obarray.set_symbol_value(
"mode-line-window-dedicated-keymap",
mode_line_window_dedicated_keymap,
);
obarray.set_symbol_value("indent-rigidly-map", indent_rigidly_map);
obarray.set_symbol_value("text-mode-map", text_mode_map);
obarray.set_symbol_value("image-slice-map", image_slice_map);
obarray.set_symbol_value("tool-bar-map", tool_bar_map);
obarray.set_symbol_value("key-translation-map", key_translation_map);
obarray.set_symbol_value("function-key-map", function_key_map);
obarray.set_symbol_value("input-decode-map", input_decode_map);
obarray.make_special("input-decode-map");
obarray.set_symbol_value("local-function-key-map", local_function_key_map);
obarray.make_special("local-function-key-map");
obarray.set_symbol_value("keyboard-translate-table", Value::NIL);
obarray.set_symbol_value("purify-flag", Value::NIL);
obarray.set_symbol_value("max-lisp-eval-depth", Value::fixnum(2400));
obarray.set_symbol_value("max-specpdl-size", Value::fixnum(1800));
obarray.set_symbol_value("inhibit-load-charset-map", Value::NIL);
obarray.set_symbol_value("standard-display-table", Value::NIL);
obarray.set_symbol_value(
"image-load-path",
Value::list(vec![
Value::string("/usr/share/emacs/30.1/etc/images/"),
Value::symbol("data-directory"),
]),
);
obarray.set_symbol_value("image-scaling-factor", Value::make_float(1.0));
obarray.set_symbol_value("user-init-file", Value::NIL);
obarray.set_symbol_value("user-emacs-directory", Value::string("~/.emacs.d/"));
obarray.set_symbol_value("frame--special-parameters", Value::NIL);
super::alloc::register_bootstrap_vars(&mut obarray);
super::load::register_bootstrap_vars(&mut obarray);
super::fileio::register_bootstrap_vars(&mut obarray);
super::window_cmds::register_bootstrap_vars(&mut obarray);
super::keyboard::pure::register_bootstrap_vars(&mut obarray);
super::composite::register_bootstrap_vars(&mut obarray);
super::coding::register_bootstrap_vars(&mut obarray);
super::xdisp::register_bootstrap_vars(&mut obarray);
super::textprop::register_bootstrap_vars(&mut obarray);
super::xfaces::register_bootstrap_vars(&mut obarray);
super::frame_vars::register_bootstrap_vars(&mut obarray);
super::buffer_vars::register_bootstrap_vars(&mut obarray);
obarray.set_symbol_value("unread-input-method-events", Value::NIL);
obarray.set_symbol_value("unread-post-input-method-events", Value::NIL);
obarray.set_symbol_value("input-method-alist", Value::NIL);
obarray.set_symbol_value("input-method-activate-hook", Value::NIL);
obarray.set_symbol_value("input-method-after-insert-chunk-hook", Value::NIL);
obarray.set_symbol_value("input-method-deactivate-hook", Value::NIL);
obarray.set_symbol_value("input-method-exit-on-first-char", Value::NIL);
obarray.set_symbol_value("input-method-exit-on-invalid-key", Value::NIL);
obarray.set_symbol_value("input-method-function", Value::NIL);
obarray.set_symbol_value("input-method-highlight-flag", Value::T);
obarray.set_symbol_value("input-method-history", Value::NIL);
obarray.set_symbol_value("input-method-use-echo-area", Value::NIL);
obarray.set_symbol_value("input-method-verbose-flag", Value::symbol("default"));
obarray.set_symbol_value("unread-command-events", Value::NIL);
for &(name, _) in STARTUP_VARIABLE_DOC_STUBS {
obarray
.put_property(name, "variable-documentation", Value::fixnum(0))
.expect("startup variable-documentation plist should always be valid");
}
for &(name, doc) in STARTUP_VARIABLE_DOC_STRING_PROPERTIES {
obarray
.put_property(name, "variable-documentation", Value::string(doc))
.expect("startup variable-documentation plist should always be valid");
}
for name in ["mark-marker", "region-beginning", "region-end"] {
obarray.set_symbol_function(name, Value::subr_from_sym_id(intern(name)));
}
obarray.clear_function_silent("word-at-point");
for name in &[
"debug-on-error",
"debugger",
"load-prefer-newer",
"load-path",
"load-history",
"default-directory",
"load-file-name",
"set-auto-coding-for-load",
"noninteractive",
"inhibit-quit",
"inhibit-read-only",
"inhibit-modification-hooks",
"internal-make-interpreted-closure-function",
"print-length",
"print-level",
"standard-output",
"case-fold-search",
"buffer-read-only",
"current-prefix-arg",
"prefix-arg",
"last-prefix-arg",
"last-command-event",
"last-input-event",
"last-command",
"real-last-command",
"this-command",
"real-this-command",
"this-command-keys-shift-translated",
"unread-command-events",
"unread-input-method-events",
"unread-post-input-method-events",
"transient-mark-mode",
] {
obarray.make_special(name);
}
super::errors::init_standard_errors(&mut obarray);
super::indent::init_indent_vars(&mut obarray);
let mut custom = CustomManager::new();
{
let id = crate::emacs_core::intern::intern("case-fold-search");
obarray.set_symbol_value("case-fold-search", Value::T);
obarray.make_symbol_localized(id, Value::T);
obarray.set_blv_local_if_set(id, true);
}
{
let id = crate::emacs_core::intern::intern("indent-tabs-mode");
obarray.make_symbol_localized(id, Value::T);
obarray.set_blv_local_if_set(id, true);
}
super::textprop::init_textprop_vars(&mut obarray, &mut custom);
super::syntax::init_syntax_vars(&mut obarray, &mut custom);
macro_rules! defvar_per_buffer {
($name:expr, $val:expr) => {
obarray.make_special($name);
obarray.set_symbol_value($name, $val);
};
}
{
defvar_per_buffer!("buffer-file-name", Value::NIL);
defvar_per_buffer!("buffer-file-truename", Value::NIL);
{
let cwd = std::env::current_dir()
.map(|p| {
let mut s = p.to_string_lossy().into_owned();
if !s.ends_with('/') {
s.push('/');
}
s
})
.unwrap_or_else(|_| "/".to_string());
defvar_per_buffer!("default-directory", Value::unibyte_string(cwd));
}
defvar_per_buffer!("buffer-read-only", Value::NIL);
defvar_per_buffer!("buffer-undo-list", Value::NIL);
defvar_per_buffer!("buffer-saved-size", Value::fixnum(0));
defvar_per_buffer!("buffer-backed-up", Value::NIL);
defvar_per_buffer!("buffer-file-format", Value::NIL);
defvar_per_buffer!("buffer-auto-save-file-name", Value::NIL);
defvar_per_buffer!("buffer-auto-save-file-format", Value::T);
defvar_per_buffer!("buffer-file-coding-system", Value::NIL);
defvar_per_buffer!("buffer-display-count", Value::fixnum(0));
defvar_per_buffer!("buffer-display-time", Value::NIL);
defvar_per_buffer!("major-mode", Value::symbol("fundamental-mode"));
defvar_per_buffer!("mode-name", Value::NIL);
defvar_per_buffer!("mode-line-format", Value::string("%-"));
defvar_per_buffer!("header-line-format", Value::NIL);
defvar_per_buffer!("tab-line-format", Value::NIL);
defvar_per_buffer!("local-abbrev-table", Value::NIL);
defvar_per_buffer!("local-minor-modes", Value::NIL);
defvar_per_buffer!("abbrev-mode", Value::NIL);
defvar_per_buffer!("overwrite-mode", Value::NIL);
defvar_per_buffer!("auto-fill-function", Value::NIL);
defvar_per_buffer!("lexical-binding", Value::NIL);
defvar_per_buffer!("case-fold-search", Value::T);
defvar_per_buffer!("indent-tabs-mode", Value::T);
defvar_per_buffer!("tab-width", Value::fixnum(8));
defvar_per_buffer!("fill-column", Value::fixnum(70));
defvar_per_buffer!("left-margin", Value::fixnum(0));
defvar_per_buffer!("truncate-lines", Value::NIL);
defvar_per_buffer!("word-wrap", Value::NIL);
defvar_per_buffer!("ctl-arrow", Value::T);
defvar_per_buffer!("selective-display", Value::NIL);
defvar_per_buffer!("selective-display-ellipses", Value::T);
defvar_per_buffer!("enable-multibyte-characters", Value::T);
defvar_per_buffer!("buffer-display-table", Value::NIL);
defvar_per_buffer!("buffer-invisibility-spec", Value::NIL);
defvar_per_buffer!("line-spacing", Value::NIL);
defvar_per_buffer!("cache-long-scans", Value::T);
defvar_per_buffer!("point-before-scroll", Value::NIL);
defvar_per_buffer!("cursor-type", Value::T);
defvar_per_buffer!("cursor-in-non-selected-windows", Value::T);
defvar_per_buffer!("mark-active", Value::NIL);
defvar_per_buffer!("bidi-display-reordering", Value::T);
defvar_per_buffer!("bidi-paragraph-direction", Value::NIL);
defvar_per_buffer!("bidi-paragraph-start-re", Value::NIL);
defvar_per_buffer!("bidi-paragraph-separate-re", Value::NIL);
defvar_per_buffer!("left-fringe-width", Value::NIL);
defvar_per_buffer!("right-fringe-width", Value::NIL);
defvar_per_buffer!("left-margin-width", Value::fixnum(0));
defvar_per_buffer!("right-margin-width", Value::fixnum(0));
defvar_per_buffer!("fringes-outside-margins", Value::NIL);
defvar_per_buffer!("fringe-indicator-alist", Value::NIL);
defvar_per_buffer!("fringe-cursor-alist", Value::NIL);
defvar_per_buffer!("indicate-empty-lines", Value::NIL);
defvar_per_buffer!("indicate-buffer-boundaries", Value::NIL);
defvar_per_buffer!("scroll-bar-width", Value::NIL);
defvar_per_buffer!("scroll-bar-height", Value::NIL);
defvar_per_buffer!("vertical-scroll-bar", Value::T);
defvar_per_buffer!("horizontal-scroll-bar", Value::T);
defvar_per_buffer!("scroll-up-aggressively", Value::NIL);
defvar_per_buffer!("scroll-down-aggressively", Value::NIL);
defvar_per_buffer!("text-conversion-style", Value::NIL);
{
use crate::buffer::buffer::BUFFER_SLOT_INFO;
use crate::emacs_core::forward::alloc_buffer_objfwd;
use crate::emacs_core::intern::intern;
for info in BUFFER_SLOT_INFO {
if !info.install_as_forwarder {
continue;
}
let id = intern(info.name);
let predicate = if info.predicate.is_empty() {
intern("null")
} else {
intern(info.predicate)
};
let fwd = alloc_buffer_objfwd(
info.offset as u16,
info.local_flags_idx,
predicate,
info.default.to_value(),
);
obarray.install_buffer_objfwd(id, fwd);
}
}
}
obarray.set_symbol_value("inhibit-changing-match-data", Value::NIL);
obarray.make_special("inhibit-changing-match-data");
{
let id = crate::emacs_core::intern::intern("case-symbols-as-words");
obarray.set_symbol_value("case-symbols-as-words", Value::NIL);
obarray.make_symbol_localized(id, Value::NIL);
obarray.set_blv_local_if_set(id, true);
}
obarray.set_symbol_value("kill-emacs-hook", Value::NIL);
obarray.make_special("kill-emacs-hook");
obarray.set_symbol_value("post-self-insert-hook", Value::NIL);
obarray.make_special("post-self-insert-hook");
let mut command_loop = crate::keyboard::CommandLoop::new();
command_loop
.keyboard
.set_terminal_translation_maps(input_decode_map, local_function_key_map);
let noninteractive = obarray
.symbol_value_id_or_nil(core_eval_symbols.noninteractive_symbol)
.is_truthy();
let symbols_with_pos_enabled = obarray
.symbol_value_id_or_nil(core_eval_symbols.symbols_with_pos_enabled_symbol)
.is_truthy();
let print_symbols_bare = obarray
.symbol_value_id_or_nil(core_eval_symbols.print_symbols_bare_symbol)
.is_truthy();
let quit_flag = obarray.symbol_value_id_or_nil(core_eval_symbols.quit_flag_symbol);
let inhibit_quit = obarray.symbol_value_id_or_nil(core_eval_symbols.inhibit_quit_symbol);
let mut ev = Self {
tagged_heap,
pdump_image: None,
obarray,
specpdl: Vec::new(),
lexenv: Value::NIL,
internal_interpreter_environment_symbol: core_eval_symbols
.internal_interpreter_environment_symbol,
quit_flag_symbol: core_eval_symbols.quit_flag_symbol,
inhibit_quit_symbol: core_eval_symbols.inhibit_quit_symbol,
throw_on_input_symbol: core_eval_symbols.throw_on_input_symbol,
kill_emacs_symbol: core_eval_symbols.kill_emacs_symbol,
quit_flag,
inhibit_quit,
noninteractive_symbol: core_eval_symbols.noninteractive_symbol,
noninteractive,
symbols_with_pos_enabled_symbol: core_eval_symbols.symbols_with_pos_enabled_symbol,
symbols_with_pos_enabled,
print_symbols_bare_symbol: core_eval_symbols.print_symbols_bare_symbol,
print_symbols_bare,
features: Vec::new(),
require_stack: Vec::new(),
loads_in_progress: Vec::new(),
buffers: BufferManager::new(),
match_data: None,
processes: ProcessManager::new(),
timers: TimerManager::new(),
watchers: VariableWatcherList::new(),
standard_syntax_table,
standard_category_table,
current_local_map: Value::NIL,
registers: RegisterManager::new(),
bookmarks: BookmarkManager::new(),
abbrevs: AbbrevManager::new(),
autoloads: AutoloadManager::new(),
custom,
rectangle: RectangleState::new(),
interactive: InteractiveRegistry::new(),
treesit: super::treesit::TreeSitterManager::new(),
minibuffers: MinibufferManager::new(),
interactive_minibuffer_read_count: 0,
current_message: None,
minibuffer_selected_window: None,
active_minibuffer_window: None,
shutdown_request: None,
input_mode_interrupt: true,
quit_char: 7,
waiting_for_user_input: false,
frames: FrameManager::new(),
modes: ModeRegistry::new(),
threads: ThreadManager::new(),
kmacro: KmacroManager::new(),
command_loop,
input_rx: None,
#[cfg(unix)]
wakeup_fd: None,
quit_requested: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
redisplay_fn: None,
display_host: None,
coding_systems: CodingSystemManager::new(),
face_table: FaceTable::new(),
face_change_count: 0,
redisplay_generation: 0,
last_redisplay_signature: None,
depth: 0,
eval_counter: 0,
max_depth: 2400, gc_pending: false,
gc_count: 0,
gc_inhibit_depth: 0,
gc_stress: false,
gc_runtime_settings_cache: GcRuntimeSettingsCache::default(),
vm_root_frames: Vec::new(),
bc_buf: Vec::with_capacity(4096),
bc_frames: Vec::new(),
condition_stack: Vec::new(),
next_resume_id: 1,
pending_safe_funcalls: Vec::new(),
named_call_cache: HashMap::with_capacity(NAMED_CALL_CACHE_CAPACITY),
lexenv_assq_cache: RefCell::new(LexenvAssqCache::default()),
lexenv_special_cache: RefCell::new(LexenvSpecialCache::default()),
macro_expansion_scope_depth: 0,
macro_expansion_mutation_epoch: 0,
macro_cache_hits: 0,
macro_cache_misses: 0,
macro_expand_total_us: 0,
macro_cache_disabled: false,
runtime_macro_expansion_cache: HashMap::new(),
macro_perf_enabled: std::env::var_os("NEOVM_TRACE_MACRO_PERF").is_some(),
macro_perf_stats: MacroPerfStats::default(),
interpreted_closure_filter_fn: None,
interpreted_closure_trim_cache: HashMap::new(),
interpreted_closure_value_cache: HashMap::new(),
};
ev.finish_runtime_activation(false);
ev
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn from_dump(
tagged_heap: Box<crate::tagged::gc::TaggedHeap>,
obarray: Obarray,
lexenv: Value,
features: Vec<SymId>,
require_stack: Vec<SymId>,
loads_in_progress: Vec<crate::heap_types::LispString>,
buffers: BufferManager,
autoloads: AutoloadManager,
custom: CustomManager,
modes: ModeRegistry,
coding_systems: CodingSystemManager,
face_table: FaceTable,
abbrevs: AbbrevManager,
interactive: InteractiveRegistry,
rectangle: RectangleState,
standard_syntax_table: Value,
standard_category_table: Value,
current_local_map: Value,
kmacro: KmacroManager,
registers: RegisterManager,
bookmarks: BookmarkManager,
watchers: VariableWatcherList,
) -> Self {
let dumped_function_surface = obarray.clone();
let mut obarray = obarray;
let core_eval_symbols = install_core_eval_symbols(&mut obarray, false);
let mut tagged_heap = tagged_heap;
crate::tagged::gc::set_tagged_heap(&mut tagged_heap);
let noninteractive = obarray
.symbol_value_id_or_nil(core_eval_symbols.noninteractive_symbol)
.is_truthy();
let symbols_with_pos_enabled = obarray
.symbol_value_id_or_nil(core_eval_symbols.symbols_with_pos_enabled_symbol)
.is_truthy();
let print_symbols_bare = obarray
.symbol_value_id_or_nil(core_eval_symbols.print_symbols_bare_symbol)
.is_truthy();
let quit_flag = obarray.symbol_value_id_or_nil(core_eval_symbols.quit_flag_symbol);
let inhibit_quit = obarray.symbol_value_id_or_nil(core_eval_symbols.inhibit_quit_symbol);
let mut ev = Self {
tagged_heap,
pdump_image: None,
obarray,
specpdl: Vec::new(),
lexenv,
internal_interpreter_environment_symbol: core_eval_symbols
.internal_interpreter_environment_symbol,
quit_flag_symbol: core_eval_symbols.quit_flag_symbol,
inhibit_quit_symbol: core_eval_symbols.inhibit_quit_symbol,
throw_on_input_symbol: core_eval_symbols.throw_on_input_symbol,
kill_emacs_symbol: core_eval_symbols.kill_emacs_symbol,
quit_flag,
inhibit_quit,
noninteractive_symbol: core_eval_symbols.noninteractive_symbol,
noninteractive,
symbols_with_pos_enabled_symbol: core_eval_symbols.symbols_with_pos_enabled_symbol,
symbols_with_pos_enabled,
print_symbols_bare_symbol: core_eval_symbols.print_symbols_bare_symbol,
print_symbols_bare,
features,
require_stack,
loads_in_progress,
buffers,
match_data: None,
processes: ProcessManager::new(),
timers: TimerManager::new(),
watchers,
standard_syntax_table,
standard_category_table,
current_local_map,
registers,
bookmarks,
abbrevs,
autoloads,
custom,
rectangle,
interactive,
treesit: super::treesit::TreeSitterManager::new(),
minibuffers: MinibufferManager::new(),
interactive_minibuffer_read_count: 0,
current_message: None,
minibuffer_selected_window: None,
active_minibuffer_window: None,
shutdown_request: None,
input_mode_interrupt: true,
quit_char: 7,
waiting_for_user_input: false,
frames: FrameManager::new(),
modes,
threads: ThreadManager::new(),
kmacro,
command_loop: crate::keyboard::CommandLoop::new(),
input_rx: None,
#[cfg(unix)]
wakeup_fd: None,
quit_requested: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
redisplay_fn: None,
display_host: None,
coding_systems,
face_table,
face_change_count: 0,
redisplay_generation: 0,
last_redisplay_signature: None,
depth: 0,
eval_counter: 0,
max_depth: 2400,
gc_pending: false,
gc_count: 0,
gc_inhibit_depth: 0,
gc_stress: false,
gc_runtime_settings_cache: GcRuntimeSettingsCache::default(),
vm_root_frames: Vec::new(),
bc_buf: Vec::with_capacity(4096),
bc_frames: Vec::new(),
condition_stack: Vec::new(),
next_resume_id: 1,
pending_safe_funcalls: Vec::new(),
named_call_cache: HashMap::with_capacity(NAMED_CALL_CACHE_CAPACITY),
lexenv_assq_cache: RefCell::new(LexenvAssqCache::default()),
lexenv_special_cache: RefCell::new(LexenvSpecialCache::default()),
macro_expansion_scope_depth: 0,
macro_expansion_mutation_epoch: 0,
macro_cache_hits: 0,
macro_cache_misses: 0,
macro_expand_total_us: 0,
macro_cache_disabled: false,
runtime_macro_expansion_cache: HashMap::new(),
macro_perf_enabled: std::env::var_os("NEOVM_TRACE_MACRO_PERF").is_some(),
macro_perf_stats: MacroPerfStats::default(),
interpreted_closure_filter_fn: None,
interpreted_closure_trim_cache: HashMap::new(),
interpreted_closure_value_cache: HashMap::new(),
};
ev.initialize_gc_stack_bottom();
ev.setup_thread_locals();
builtins::init_builtins(&mut ev);
for (sym_id, symbol) in dumped_function_surface.iter_symbols() {
if !symbol.function.is_nil() {
ev.obarray.set_symbol_function_id(sym_id, symbol.function);
} else if dumped_function_surface.is_function_unbound_id(sym_id) {
ev.obarray.fmakunbound_id(sym_id);
} else {
ev.obarray.clear_function_silent_id(sym_id);
}
}
ev.finish_runtime_activation(true);
ev
}
pub(crate) fn install_pdump_image(&mut self, image: super::pdump::mmap_image::LoadedMmapImage) {
self.pdump_image = Some(image);
}
#[cfg(test)]
pub(crate) fn pdump_image_contains_ptr(&self, ptr: *const u8) -> bool {
self.pdump_image
.as_ref()
.is_some_and(|image| image.contains_ptr(ptr))
}
fn trace_roots(&self, visit: &mut dyn FnMut(Value)) {
for frame in &self.vm_root_frames {
for root in frame.roots.iter().copied() {
visit(root);
}
}
for root in self.treesit.roots() {
visit(root);
}
for root in self.bc_buf.iter().copied() {
visit(root);
}
for frame in &self.bc_frames {
if frame.fun.is_heap_object() {
visit(frame.fun);
}
}
for frame in &self.condition_stack {
match frame {
ConditionFrame::Catch { tag, .. } => visit(*tag),
ConditionFrame::ConditionCase { conditions, .. } => visit(*conditions),
ConditionFrame::HandlerBind {
conditions,
handler,
..
} => {
visit(*conditions);
visit(*handler);
}
ConditionFrame::SkipConditions { .. } => {}
}
}
for entry in &self.specpdl {
match entry {
SpecBinding::Let {
old_value: Some(val),
..
} => visit(*val),
SpecBinding::LetLocal { old_value, .. } => visit(*old_value),
SpecBinding::LetDefault {
old_value: Some(val),
..
} => visit(*val),
SpecBinding::LexicalEnv { old_lexenv } => visit(*old_lexenv),
SpecBinding::GcRoot { value } => visit(*value),
SpecBinding::Backtrace { function, args, .. } => {
visit(*function);
for arg in args.as_slice().iter().copied() {
visit(arg);
}
}
SpecBinding::UnwindProtect { forms, lexenv } => {
visit(*forms);
visit(*lexenv);
}
SpecBinding::SaveRestriction { state } => {
let mut roots = Vec::new();
state.trace_roots(&mut roots);
for root in roots {
visit(root);
}
}
SpecBinding::SaveExcursion { marker, .. } => visit(*marker),
SpecBinding::SaveCurrentBuffer { .. } | SpecBinding::Nop => {}
_ => {}
}
}
visit(self.lexenv);
visit(self.quit_flag);
visit(self.inhibit_quit);
for entry in self.runtime_macro_expansion_cache.values() {
visit(entry.function);
visit(entry.expanded);
}
for bucket in self.interpreted_closure_trim_cache.values() {
for entry in bucket {
visit(entry.params_value);
visit(entry.body_value);
visit(entry.iform_value);
visit(entry.trimmed_params_value);
visit(entry.trimmed_body_value);
}
}
for bucket in self.interpreted_closure_value_cache.values() {
for entry in bucket {
visit(entry.source_function);
visit(entry.trimmed_params_value);
visit(entry.trimmed_body_value);
}
}
if let Some(filter_fn) = self.interpreted_closure_filter_fn {
visit(filter_fn);
}
for entry in self.named_call_cache.values() {
if let NamedCallTarget::Obarray(val) = &entry.target {
visit(*val);
}
}
for funcall in &self.pending_safe_funcalls {
visit(funcall.function);
for arg in funcall.args.iter().copied() {
visit(arg);
}
}
let mut thread_local_roots = Vec::new();
collect_thread_local_gc_roots(&mut thread_local_roots);
for root in thread_local_roots {
visit(root);
}
if !self.current_local_map.is_nil() {
visit(self.current_local_map);
}
if self.standard_syntax_table.is_heap_object() {
visit(self.standard_syntax_table);
}
if self.standard_category_table.is_heap_object() {
visit(self.standard_category_table);
}
self.obarray.trace_roots_with(visit);
self.processes.trace_roots_with(visit);
self.timers.trace_roots_with(visit);
self.watchers.trace_roots_with(visit);
self.registers.trace_roots_with(visit);
self.custom.trace_roots_with(visit);
self.autoloads.trace_roots_with(visit);
self.buffers.trace_roots_with(visit);
self.face_table.trace_roots_with(visit);
self.threads.trace_roots_with(visit);
self.kmacro.trace_roots_with(visit);
crate::gc_trace::GcTrace::trace_roots_with(&self.command_loop, visit);
self.modes.trace_roots_with(visit);
self.frames.trace_roots_with(visit);
self.coding_systems.trace_roots_with(visit);
if let Some(ref md) = self.match_data
&& let Some(crate::emacs_core::regex::SearchedString::Heap(val)) = &md.searched_string
{
visit(*val);
}
}
pub fn gc_threshold(&self) -> usize {
self.tagged_heap.gc_threshold()
}
fn is_gc_runtime_setting_symbol(sym_id: SymId) -> bool {
sym_id == gc_cons_threshold_symbol()
|| sym_id == gc_cons_percentage_symbol()
|| sym_id == memory_full_symbol()
}
pub(crate) fn refresh_gc_runtime_settings_after_change_by_id(&mut self, sym_id: SymId) {
if Self::is_gc_runtime_setting_symbol(sym_id) {
self.refresh_gc_runtime_settings_cache();
self.sync_gc_threshold_from_runtime_settings();
}
}
fn refresh_gc_runtime_settings_cache(&mut self) {
self.gc_runtime_settings_cache.gc_cons_threshold_bytes = self
.obarray
.symbol_value_id(gc_cons_threshold_symbol())
.copied()
.and_then(|value| value.as_fixnum())
.and_then(|n| usize::try_from(n).ok())
.unwrap_or(GC_DEFAULT_THRESHOLD_BYTES);
self.gc_runtime_settings_cache.gc_cons_percentage_scaled = self
.obarray
.symbol_value_id_or_nil(gc_cons_percentage_symbol())
.as_number_f64()
.filter(|float| float.is_finite() && *float > 0.0)
.map(|float| ((float * GC_PERCENT_SCALE as f64).ceil() as u64).clamp(1, u64::MAX));
self.gc_runtime_settings_cache.memory_full = !self
.obarray
.symbol_value_id_or_nil(memory_full_symbol())
.is_nil();
}
fn effective_gc_threshold_bytes(&mut self) -> usize {
if self.gc_runtime_settings_cache.memory_full {
return self.tagged_heap.gc_threshold();
}
let mut threshold = self
.gc_runtime_settings_cache
.gc_cons_threshold_bytes
.max(GC_THRESHOLD_FLOOR_BYTES);
if let Some(percentage_scaled) = self.gc_runtime_settings_cache.gc_cons_percentage_scaled {
let live_estimate = self
.tagged_heap
.live_bytes()
.saturating_add(self.tagged_heap.bytes_since_gc() / 2);
let pct_threshold = ((live_estimate as u128)
.saturating_mul(percentage_scaled as u128)
.saturating_add((GC_PERCENT_SCALE - 1) as u128)
/ GC_PERCENT_SCALE as u128)
.min(GC_HI_THRESHOLD_BYTES as u128) as usize;
threshold = threshold.max(pct_threshold);
}
threshold.clamp(1, GC_HI_THRESHOLD_BYTES)
}
fn sync_gc_threshold_from_runtime_settings(&mut self) {
let threshold = self.effective_gc_threshold_bytes();
if self.tagged_heap.gc_threshold() != threshold {
self.tagged_heap.set_gc_threshold_from_runtime(threshold);
}
}
fn update_gc_runtime_stats(&mut self, elapsed: std::time::Duration) {
self.obarray
.set_symbol_value_id(gcs_done_symbol(), Value::fixnum(self.gc_count as i64));
let old_elapsed = self
.obarray
.symbol_value_id(gc_elapsed_symbol())
.copied()
.and_then(|value| value.as_number_f64())
.unwrap_or(0.0);
self.obarray.set_symbol_value_id(
gc_elapsed_symbol(),
Value::make_float(old_elapsed + elapsed.as_secs_f64()),
);
}
pub fn set_gc_threshold(&mut self, threshold: usize) {
self.tagged_heap.set_gc_threshold(threshold);
}
pub fn set_max_depth(&mut self, depth: usize) {
self.max_depth = depth;
}
pub fn setup_thread_locals(&mut self) {
crate::tagged::gc::set_tagged_heap(&mut self.tagged_heap);
super::syntax::restore_standard_syntax_table_object(self.standard_syntax_table);
super::category::restore_standard_category_table_object(self.standard_category_table);
QUIT_REQUESTED_TLS.with(|cell| {
*cell.borrow_mut() = Some(std::sync::Arc::clone(&self.quit_requested));
});
}
fn initialize_gc_stack_bottom(&mut self) {
#[cfg(target_os = "linux")]
{
if let Some(stack_end) = crate::tagged::gc::read_stack_end_from_proc() {
self.tagged_heap.set_stack_bottom(stack_end as *const u8);
}
}
}
fn finish_runtime_activation(&mut self, sync_keyboard: bool) {
self.setup_thread_locals();
self.refresh_gc_runtime_settings_cache();
self.sync_gc_threshold_from_runtime_settings();
if sync_keyboard {
self.sync_keyboard_runtime_from_obarray();
}
self.sync_thread_runtime_bindings();
self.sync_current_thread_buffer_state();
}
pub(crate) fn sync_current_thread_buffer_state(&mut self) {
let current_thread_id = self.threads.current_thread_id();
let current_buffer_id = self.buffers.current_buffer_id();
self.threads
.set_thread_current_buffer(current_thread_id, current_buffer_id);
}
fn sync_current_buffer_runtime_state(&mut self) -> Result<(), Flow> {
self.sync_current_thread_buffer_state();
super::casetab::sync_current_buffer_case_table_state(self)?;
super::syntax::sync_current_buffer_syntax_table_state(self)?;
Ok(())
}
pub(crate) fn switch_current_buffer(
&mut self,
id: crate::buffer::BufferId,
) -> Result<(), Flow> {
if !self.buffers.switch_current(id) {
return Err(signal(
"error",
vec![Value::string("Selecting deleted buffer")],
));
}
self.sync_current_buffer_runtime_state()
}
pub(crate) fn set_current_buffer_unrecorded(
&mut self,
id: crate::buffer::BufferId,
) -> Result<(), Flow> {
if !self.buffers.switch_current_unrecorded(id) {
return Err(signal(
"error",
vec![Value::string("Selecting deleted buffer")],
));
}
self.sync_current_buffer_runtime_state()
}
pub fn restore_current_buffer_if_live(&mut self, id: crate::buffer::BufferId) {
if self.buffers.get(id).is_none() {
return;
}
let _ = self.buffers.switch_current_unrecorded(id);
let _ = self.sync_current_buffer_runtime_state();
}
#[cfg(unix)]
pub fn init_input_system(
&mut self,
input_rx: crossbeam_channel::Receiver<crate::keyboard::InputEvent>,
wakeup_fd: std::os::unix::io::RawFd,
) {
self.input_rx = Some(input_rx);
self.wakeup_fd = Some(wakeup_fd);
self.command_loop.running = true;
}
pub fn set_display_host(&mut self, host: Box<dyn DisplayHost>) {
self.display_host = Some(host);
}
#[tracing::instrument(skip_all)]
pub fn recursive_edit(&mut self) -> Result<(), String> {
match self.recursive_edit_inner() {
Ok(_) => Ok(()),
Err(Flow::Signal(sig)) if sig.symbol == self.kill_emacs_symbol => Ok(()),
Err(flow) => Err(format!("{:?}", flow)),
}
}
pub(crate) fn request_shutdown(&mut self, exit_code: i32, restart: bool) {
self.shutdown_request = Some(ShutdownRequest { exit_code, restart });
self.command_loop.running = false;
}
pub fn shutdown_request(&self) -> Option<ShutdownRequest> {
self.shutdown_request
}
#[tracing::instrument(skip_all, fields(depth = self.command_loop.recursive_depth, has_input = self.input_rx.is_some()))]
pub(crate) fn recursive_edit_inner(&mut self) -> EvalResult {
self.run_exit_wrapped_command_loop(true)
}
#[tracing::instrument(skip_all, fields(depth = self.command_loop.recursive_depth, has_input = self.input_rx.is_some()))]
pub(crate) fn minibuffer_command_loop_inner(&mut self) -> EvalResult {
self.run_exit_wrapped_command_loop(false)
}
fn run_exit_wrapped_command_loop(&mut self, increment_depth: bool) -> EvalResult {
if self.input_rx.is_none() && !self.command_loop_noninteractive() {
tracing::info!("recursive_edit_inner: no input receiver, returning immediately");
return Ok(Value::NIL);
}
let saved_running = self.command_loop.running;
if !saved_running {
self.command_loop.running = true;
}
if increment_depth {
self.command_loop.recursive_depth += 1;
}
let exit_tag = Value::symbol("exit");
self.push_condition_frame(ConditionFrame::Catch {
tag: exit_tag,
resume: ResumeTarget::CommandLoopExit,
});
let result = self.command_loop_inner();
self.pop_condition_frame();
if increment_depth {
self.command_loop.recursive_depth -= 1;
}
if !saved_running {
self.command_loop.running = false;
}
match result {
Ok(val) => Ok(val),
Err(Flow::Throw { ref tag, ref value }) if tag.is_symbol_named("exit") => {
if value.is_truthy() {
Err(super::error::signal("quit", vec![]))
} else {
Ok(Value::NIL)
}
}
Err(flow) => Err(flow),
}
}
#[tracing::instrument(skip_all)]
fn command_loop_inner(&mut self) -> EvalResult {
let outermost_command_loop =
self.command_loop.recursive_depth == 1 && self.minibuffers.depth() == 0;
loop {
let top_level_tag = Value::symbol("top-level");
self.push_condition_frame(ConditionFrame::Catch {
tag: top_level_tag,
resume: ResumeTarget::CommandLoopTopLevel,
});
let result = if outermost_command_loop {
match self.command_loop_top_level_1() {
Ok(_) => self.command_loop_2(),
Err(Flow::Throw { ref tag, .. }) if tag.is_symbol_named("top-level") => {
self.command_loop_2()
}
Err(flow) => Err(flow),
}
} else {
self.command_loop_2()
};
self.pop_condition_frame();
match result {
Err(Flow::Throw { ref tag, .. }) if tag.is_symbol_named("top-level") => {
tracing::debug!("command_loop_inner: top-level throw, restarting loop");
continue;
}
Ok(value) if outermost_command_loop && self.command_loop_noninteractive() => {
tracing::info!("command_loop_inner: noninteractive EOF, calling kill-emacs");
match super::builtins::symbols::builtin_kill_emacs(self, vec![Value::T]) {
Err(Flow::Signal(sig)) if sig.symbol == self.kill_emacs_symbol => {}
Ok(_) => {}
Err(flow) => return Err(flow),
}
return Ok(value);
}
other => {
tracing::debug!(
"command_loop_inner: result={:?}, propagating",
other.is_ok()
);
return other;
}
}
}
}
fn command_loop_noninteractive(&self) -> bool {
self.noninteractive
}
fn command_loop_top_level_1(&mut self) -> EvalResult {
let top_level = self
.obarray
.symbol_value("top-level")
.copied()
.unwrap_or(Value::NIL);
tracing::debug!("command_loop_top_level_1: top-level={}", top_level);
if top_level.is_nil() {
tracing::debug!("command_loop_top_level_1: top-level is nil, skipping");
self.log_startup_state("top-level-nil");
return Ok(Value::NIL);
}
tracing::debug!("command_loop_top_level_1: evaluating top-level form");
self.log_startup_state("top-level-before");
match self.eval_value(&top_level) {
Ok(_) => {
tracing::debug!("command_loop_top_level_1: top-level completed OK");
self.log_startup_state("top-level-after");
Ok(Value::NIL)
}
Err(Flow::Signal(sig)) => {
if sig.symbol == self.kill_emacs_symbol {
return Err(Flow::Signal(sig));
}
tracing::warn!(
"command_loop_top_level_1: top-level SIGNALED: {} {:?}",
sig.symbol_name(),
sig.data
);
let data_str = sig
.data
.iter()
.map(|value| format!("{value}"))
.collect::<Vec<_>>()
.join(" ");
let error_msg = if data_str.is_empty() {
sig.symbol_name().to_string()
} else {
format!("{}: {}", sig.symbol_name(), data_str)
};
if cfg!(test) {
let last_phase = self
.obarray
.symbol_value("neomacs--startup-last-phase")
.copied()
.map(|value| crate::emacs_core::print_value_with_eval(self, &value))
.unwrap_or_else(|| "nil".to_string());
let last_call = self
.obarray
.symbol_value("neomacs--startup-last-call")
.copied()
.map(|value| crate::emacs_core::print_value_with_eval(self, &value))
.unwrap_or_else(|| "nil".to_string());
eprintln!(
"top-level startup signal: {} last-phase={} last-call={}",
error_msg, last_phase, last_call
);
}
let _ = super::builtins::dispatch_builtin(
self,
"message",
vec![Value::string(&error_msg)],
);
self.log_startup_state("top-level-signal");
tracing::warn!("Top-level startup error: {}", error_msg);
Ok(Value::NIL)
}
Err(flow) => Err(flow),
}
}
fn trace_startup_state_enabled(&self) -> bool {
std::env::var("NEOMACS_TRACE_STARTUP_STATE")
.ok()
.is_some_and(|value| value == "1")
}
fn log_startup_state(&self, phase: &str) {
if !self.trace_startup_state_enabled() {
return;
}
let current_buffer = self
.buffers
.current_buffer()
.map(|buffer| buffer.name_runtime_string_owned())
.unwrap_or_else(|| "<none>".to_string());
let selected_frame = self.frames.selected_frame().map(|frame| {
let selected_window_buffer = frame
.selected_window()
.and_then(|window| window.buffer_id())
.and_then(|buffer_id| self.buffers.get(buffer_id))
.map(|buffer| buffer.name_runtime_string_owned())
.unwrap_or_else(|| "<missing>".to_string());
format!(
"id=0x{:x} size={}x{} selected-window=0x{:x} selected-window-buffer={}",
frame.id.0,
frame.width,
frame.height,
frame.selected_window.0,
selected_window_buffer
)
});
let frames = self
.frames
.frame_list()
.into_iter()
.map(|fid| format!("0x{:x}", fid.0))
.collect::<Vec<_>>();
tracing::info!(
"startup-state phase={} command-line-args={} command-line-args-left={} command-line-processed={} window-system={} initial-window-system={} current-buffer={} selected-frame={:?} frames={:?}",
phase,
format_startup_value(self.obarray.symbol_value("command-line-args")),
format_startup_value(self.obarray.symbol_value("command-line-args-left")),
format_startup_value(self.obarray.symbol_value("command-line-processed")),
format_startup_value(self.obarray.symbol_value("window-system")),
format_startup_value(self.obarray.symbol_value("initial-window-system")),
current_buffer,
selected_frame,
frames
);
}
#[tracing::instrument(skip_all)]
fn command_loop_2(&mut self) -> EvalResult {
loop {
match self.command_loop_1() {
Ok(val) => return Ok(val),
Err(flow @ Flow::Throw { .. }) => {
return Err(flow);
}
Err(flow @ Flow::Signal(_))
if self
.command_loop
.keyboard
.kboard
.executing_kbd_macro
.is_some() =>
{
return Err(flow);
}
Err(Flow::Signal(sig)) => {
let sym_name = sig.symbol_name().to_string();
let error_msg = self.display_command_error(&sig);
tracing::error!("Command loop error: {}", error_msg);
self.assign("prefix-arg", Value::NIL);
if sym_name == "quit" {
let _ = super::builtins::dispatch_builtin(self, "ding", vec![]);
}
continue;
}
}
}
}
fn display_command_error(&mut self, sig: &SignalData) -> String {
let error_data = make_signal_binding_value(sig);
let error_msg =
crate::emacs_core::errors::builtin_error_message_string(self, vec![error_data])
.ok()
.and_then(|value| value.as_runtime_string_owned())
.unwrap_or_else(|| sig.symbol_name().to_string());
let _ = super::builtins::dispatch_builtin(self, "message", vec![Value::string(&error_msg)]);
error_msg
}
#[tracing::instrument(skip_all)]
fn command_loop_1(&mut self) -> EvalResult {
if !self.command_loop.running {
return Ok(Value::NIL);
}
self.command_loop_1_entry_prologue()?;
loop {
if !self.command_loop.running {
return Ok(Value::NIL);
}
self.flush_pending_safe_funcalls();
self.sync_current_buffer_to_selected_window();
if self.executing_kbd_macro_iteration_complete_for_command_loop() {
self.assign("this-command", Value::NIL);
return Ok(Value::NIL);
}
let outgoing_prefix_arg = self.eval_symbol("current-prefix-arg").unwrap_or(Value::NIL);
self.assign("last-prefix-arg", outgoing_prefix_arg);
let (keys, binding) = self.read_key_sequence()?;
self.sync_current_buffer_to_selected_window();
if keys.is_empty() && binding.is_nil() {
self.assign("this-command", Value::NIL);
return Ok(Value::NIL);
}
if binding.is_nil() {
self.assign("prefix-arg", Value::NIL);
let desc: Vec<String> = keys.iter().map(|v| format!("{:?}", v)).collect();
tracing::info!("Undefined key sequence: {}", desc.join(" "));
continue;
}
tracing::info!("command_loop_1: dispatching binding={}", binding);
let previous_this_command = self.eval_symbol("this-command").unwrap_or(Value::NIL);
self.assign("real-last-command", previous_this_command);
self.assign("real-this-command", binding);
let remapped = self.command_remapping_for_loop(binding);
self.assign("this-command", remapped);
if self
.eval_symbol("this-original-command")
.unwrap_or(Value::NIL)
.is_nil()
{
self.assign("this-original-command", binding);
}
if let Some(last) = keys.last() {
self.assign("last-command-event", *last);
}
tracing::debug!(
"command_loop_1: binding={} current_buffer={:?} active_minibuffer_window={:?}",
self.this_command_name_for_log(),
self.buffers.current_buffer_id(),
self.active_minibuffer_window
);
self.safe_run_hook_if_bound("pre-command-hook")?;
if self.obarray.fboundp("undo-auto--add-boundary") {
let _ = self.apply(Value::symbol("undo-auto--add-boundary"), vec![]);
}
self.record_recent_command(remapped);
let exec_result = self.dispatch_command_in_loop(binding);
self.sync_current_buffer_to_selected_window();
if let Err(ref flow) = exec_result {
match flow {
Flow::Throw { .. } => return exec_result,
Flow::Signal(_)
if self
.command_loop
.keyboard
.kboard
.executing_kbd_macro
.is_some() =>
{
return exec_result;
}
Flow::Signal(sig) => {
let error_msg = self.display_command_error(sig);
tracing::warn!("Command error: ({}): {}", sig.symbol_name(), error_msg);
}
}
}
if let Ok(this_cmd) = self.eval_symbol("this-command") {
self.assign("last-command", this_cmd);
}
let real_this = self.eval_symbol("real-this-command").unwrap_or(Value::NIL);
let is_self_insert = real_this
.as_symbol_name()
.is_some_and(|n| n == "self-insert-command");
let last_repeatable = if is_self_insert {
self.eval_symbol("real-last-command").unwrap_or(Value::NIL)
} else {
real_this
};
self.assign("last-repeatable-command", last_repeatable);
let _ = self.update_active_region_selection_after_command();
self.safe_run_hook_if_bound("post-command-hook")?;
self.assign("this-original-command", Value::NIL);
if exec_result.is_ok()
&& self.command_loop.keyboard.kboard.defining_kbd_macro
&& self
.eval_symbol("prefix-arg")
.unwrap_or(Value::NIL)
.is_nil()
{
self.finalize_kbd_macro_runtime_chars();
}
self.command_loop_1_maybe_auto_save();
}
}
fn command_loop_1_entry_prologue(&mut self) -> EvalResult {
self.assign("prefix-arg", Value::NIL);
self.assign("last-prefix-arg", Value::NIL);
self.assign("deactivate-mark", Value::NIL);
if self
.eval_symbol("memory-full")
.unwrap_or(Value::NIL)
.is_nil()
{
self.safe_run_hook_if_bound("post-command-hook")?;
if self
.eval_symbol("delayed-warnings-list")
.unwrap_or(Value::NIL)
.is_truthy()
{
self.safe_run_hook_if_bound("delayed-warnings-hook")?;
}
}
let this_command = self.eval_symbol("this-command").unwrap_or(Value::NIL);
self.assign("last-command", this_command);
let real_this_command = self.eval_symbol("real-this-command").unwrap_or(Value::NIL);
self.assign("real-last-command", real_this_command);
let last_command_event = self.eval_symbol("last-command-event").unwrap_or(Value::NIL);
if !last_command_event.is_cons() {
self.assign("last-repeatable-command", real_this_command);
}
Ok(Value::NIL)
}
fn command_loop_1_maybe_auto_save(&mut self) {
let interval = match self.eval_symbol("auto-save-interval").ok() {
Some(v) => match v.as_fixnum() {
Some(n) if n > 0 => n as u64,
_ => return,
},
None => return,
};
let threshold = interval.max(20);
let current = self.command_loop.num_nonmacro_input_events;
let last = self.command_loop.last_auto_save_input_events;
if current.saturating_sub(last) <= threshold {
return;
}
if self.input_pending_for_auto_save() {
return;
}
self.command_loop.last_auto_save_input_events = current;
if let Err(flow) = self.apply(Value::symbol("do-auto-save"), vec![Value::NIL, Value::NIL]) {
tracing::warn!("auto-save from command_loop_1 failed: {:?}", flow);
}
}
fn input_pending_for_auto_save(&self) -> bool {
if self.peek_unread_command_event().is_some() {
return true;
}
!self.command_loop.keyboard.pending_input_events.is_empty()
}
fn command_remapping_for_loop(&mut self, command: Value) -> Value {
if command.is_nil() {
return command;
}
match self.apply(Value::symbol("command-remapping"), vec![command]) {
Ok(remapped) if !remapped.is_nil() => remapped,
_ => command,
}
}
fn dispatch_command_in_loop(&mut self, command: Value) -> EvalResult {
let cmd = self.eval_symbol("this-command").unwrap_or(command);
if cmd.is_nil() {
return Ok(Value::NIL);
}
self.apply(Value::symbol("command-execute"), vec![cmd])
}
fn safe_run_hook_if_bound(&mut self, hook_name: &str) -> EvalResult {
let hook_sym = super::intern::intern(hook_name);
super::hook_runtime::safe_run_named_hook(self, hook_sym, &[])
}
fn executing_kbd_macro_iteration_complete_for_command_loop(&self) -> bool {
matches!(
self.command_loop.keyboard.kboard.executing_kbd_macro.as_ref(),
Some(events) if self.command_loop.keyboard.kboard.kbd_macro_index >= events.len()
) && self
.command_loop
.keyboard
.kboard
.unread_selection_event
.is_none()
&& self.command_loop.keyboard.kboard.unread_events.is_empty()
}
pub(crate) fn execute_kbd_macro_iteration_via_command_loop(&mut self) -> EvalResult {
let saved_running = self.command_loop.running;
if !saved_running {
self.command_loop.running = true;
}
self.assign("prefix-arg", Value::NIL);
let result = self.command_loop_2();
if !saved_running && self.command_loop.running {
self.command_loop.running = false;
}
result
}
pub(crate) fn with_executing_kbd_macro_runtime<F>(
&mut self,
macro_events: Vec<Value>,
run: F,
) -> EvalResult
where
F: FnOnce(&mut Self) -> EvalResult,
{
let scope = ExecutingKbdMacroRuntimeScope {
snapshot: self.snapshot_executing_kbd_macro_runtime(),
real_this_command: self.eval_symbol("real-this-command").unwrap_or(Value::NIL),
};
self.begin_executing_kbd_macro_runtime(macro_events);
let result = run(self);
let cleanup = self.finish_executing_kbd_macro_runtime_scope(scope);
match cleanup {
Ok(v) if v.is_nil() => result,
Ok(other) => Ok(other),
Err(flow) => Err(flow),
}
}
pub(crate) fn reset_executing_kbd_macro_runtime_iteration(&mut self) {
self.set_executing_kbd_macro_runtime_index(0);
}
fn finish_executing_kbd_macro_runtime_scope(
&mut self,
scope: ExecutingKbdMacroRuntimeScope,
) -> EvalResult {
self.restore_executing_kbd_macro_runtime(scope.snapshot);
self.assign("real-this-command", scope.real_this_command);
self.run_hook_if_bound("kbd-macro-termination-hook")
}
fn pending_gnu_timer(timer: Value) -> Option<PendingGnuTimer> {
if !timer.is_vector() {
return None;
};
let slots = timer.as_vector_data()?.clone();
if !(9..=10).contains(&slots.len()) {
return None;
}
if !slots[0].is_nil() {
return None;
}
if !slots[7].is_nil() {
return None;
}
Some(PendingGnuTimer {
timer,
when: GnuTimerTimestamp {
high_seconds: slots[1].as_int()?,
low_seconds: slots[2].as_int()?,
usecs: slots[3].as_int()?,
psecs: slots.get(8).and_then(|v| v.as_int()).unwrap_or(0),
},
})
}
fn pending_gnu_idle_timer(timer: Value) -> Option<PendingGnuTimer> {
if !timer.is_vector() {
return None;
};
let slots = timer.as_vector_data()?.clone();
if !(9..=10).contains(&slots.len()) {
return None;
}
if !slots[0].is_nil() {
return None;
}
if slots[7].is_nil() {
return None;
}
Some(PendingGnuTimer {
timer,
when: GnuTimerTimestamp {
high_seconds: slots[1].as_int()?,
low_seconds: slots[2].as_int()?,
usecs: slots[3].as_int()?,
psecs: slots.get(8).and_then(|v| v.as_int()).unwrap_or(0),
},
})
}
pub(crate) fn run_hook_if_bound(&mut self, hook_name: &str) -> EvalResult {
match self.eval_symbol(hook_name) {
Ok(hook_val) if !hook_val.is_nil() => {
super::builtins::dispatch_builtin(self, "run-hooks", vec![Value::symbol(hook_name)])
.unwrap_or(Ok(Value::NIL))
}
_ => Ok(Value::NIL),
}
}
pub(crate) fn queue_pending_safe_funcall(&mut self, function: Value, args: Vec<Value>) {
self.pending_safe_funcalls.push(PendingSafeFuncall {
function,
args: args.into_iter().collect(),
});
}
pub(crate) fn queue_pending_safe_hook(&mut self, hook_name: &str, args: &[Value]) {
self.queue_pending_safe_funcall(
Value::symbol("run-hook-with-args"),
std::iter::once(Value::symbol(hook_name))
.chain(args.iter().copied())
.collect(),
);
}
pub(crate) fn flush_pending_safe_funcalls(&mut self) {
while let Some(funcall) = self.pending_safe_funcalls.pop() {
let _ = self.apply(funcall.function, funcall.args);
}
}
fn update_active_region_selection_after_command(&mut self) -> EvalResult {
if self
.eval_symbol("mark-active")
.unwrap_or(Value::NIL)
.is_nil()
{
return Ok(Value::NIL);
}
let transient_mark_mode = self
.eval_symbol("transient-mark-mode")
.unwrap_or(Value::NIL);
if transient_mark_mode == Value::symbol("identity") {
self.assign("transient-mark-mode", Value::NIL);
} else if transient_mark_mode == Value::symbol("only") {
self.assign("transient-mark-mode", Value::symbol("identity"));
}
if !self
.eval_symbol("deactivate-mark")
.unwrap_or(Value::NIL)
.is_nil()
{
let _ = self.apply(Value::symbol("deactivate-mark"), vec![])?;
self.assign("saved-region-selection", Value::NIL);
return Ok(Value::NIL);
}
if self
.apply(Value::symbol("display-selections-p"), vec![])?
.is_nil()
{
self.assign("saved-region-selection", Value::NIL);
return Ok(Value::NIL);
}
if self
.eval_symbol("select-active-regions")
.unwrap_or(Value::NIL)
.is_nil()
{
self.assign("saved-region-selection", Value::NIL);
return Ok(Value::NIL);
}
if self
.apply(Value::symbol("region-active-p"), vec![])?
.is_nil()
{
self.assign("saved-region-selection", Value::NIL);
return Ok(Value::NIL);
}
let this_command = self.eval_symbol("this-command").unwrap_or(Value::NIL);
let inhibited_commands = self
.eval_symbol("selection-inhibit-update-commands")
.unwrap_or(Value::NIL);
if self
.apply(
Value::symbol("memq"),
vec![this_command, inhibited_commands],
)?
.is_truthy()
{
self.assign("saved-region-selection", Value::NIL);
return Ok(Value::NIL);
}
let region_extract = self
.eval_symbol("region-extract-function")
.unwrap_or(Value::symbol("buffer-substring"));
let text = self.apply(region_extract, vec![Value::NIL])?;
let text_len = match self.apply(Value::symbol("length"), vec![text])?.kind() {
ValueKind::Fixnum(len) => len,
_ => 0,
};
if text_len > 0 {
let _ = self.apply(
Value::symbol("gui-set-selection"),
vec![Value::symbol("PRIMARY"), text],
)?;
}
let _ = super::builtins::dispatch_builtin(
self,
"run-hook-with-args",
vec![Value::symbol("post-select-region-hook"), text],
)
.unwrap_or(Ok(Value::NIL))?;
self.assign("saved-region-selection", Value::NIL);
Ok(Value::NIL)
}
pub(crate) fn redisplay(&mut self) {
self.redisplay_with_force(false);
}
pub(crate) fn redisplay_for_input_wait(&mut self) {
self.redisplay_with_force(false);
}
pub(crate) fn invalidate_redisplay(&mut self) {
self.redisplay_generation = self.redisplay_generation.wrapping_add(1);
self.last_redisplay_signature = None;
}
pub(crate) fn redisplay_with_force(&mut self, force: bool) {
let inhibit_redisplay = self.obarray.symbol_value("inhibit-redisplay");
if !force && inhibit_redisplay.as_ref().is_some_and(|v| v.is_truthy()) {
tracing::debug!(
"redisplay inhibited by inhibit-redisplay={}",
inhibit_redisplay.as_ref().unwrap()
);
return;
}
self.sync_pending_resize_events();
if let Some(buffer) = self.buffers.current_buffer() {
if buffer.is_modified() {
let pt = buffer.point_char();
if let Some(frame) = self.frames.selected_frame_mut() {
if let Some(win) = frame.selected_window_mut() {
win.set_point(pt);
}
}
}
}
let before_signature = self.redisplay_signature();
if !force && self.last_redisplay_signature.as_ref() == Some(&before_signature) {
tracing::debug!("redisplay skipped: visible state unchanged");
return;
}
let has_fn = self.redisplay_fn.is_some();
tracing::debug!("redisplay called (has_fn={})", has_fn);
if let Some(mut f) = self.redisplay_fn.take() {
let saved = self.buffers.reset_outermost_restrictions();
f(self);
let _ = super::builtins::run_redisplay_window_change_hooks(self);
self.buffers.restore_outermost_restrictions(saved);
self.redisplay_fn = Some(f);
} else {
let _ = super::builtins::run_redisplay_window_change_hooks(self);
}
self.last_redisplay_signature = Some(self.redisplay_signature());
}
fn redisplay_signature(&self) -> RedisplaySignature {
let selected_frame = self.frames.selected_frame().map(|frame| frame.id.0);
let selected_window = self
.frames
.selected_frame()
.map(|frame| frame.selected_window.0);
let frame = self.frames.selected_frame().map(|frame| {
let mut window_ids = frame.window_list();
if let Some(minibuffer_window) = frame.minibuffer_window {
window_ids.push(minibuffer_window);
}
let mut windows = Vec::with_capacity(window_ids.len());
for window_id in window_ids {
let Some(window) = frame.find_window(window_id) else {
continue;
};
let Some(state) = window.redisplay_state() else {
continue;
};
windows.push(RedisplayWindowSignature {
id: state.id.0,
buffer_id: state.buffer_id.0,
bounds: state.bounds,
window_start: state.window_start,
window_end_pos: state.window_end_pos,
window_end_bytepos: state.window_end_bytepos,
window_end_vpos: state.window_end_vpos,
window_end_valid: state.window_end_valid,
point: state.point,
old_point: state.old_point,
hscroll: state.hscroll,
vscroll: state.vscroll,
preserve_vscroll_p: state.preserve_vscroll_p,
buffer: self.redisplay_buffer_signature(state.buffer_id),
});
}
RedisplayFrameSignature {
id: frame.id.0,
width: frame.width,
height: frame.height,
char_width: redisplay_f32_bits(frame.char_width),
char_height: redisplay_f32_bits(frame.char_height),
font_pixel_size: redisplay_f32_bits(frame.font_pixel_size),
visible: frame.visible,
selected_window: frame.selected_window.0,
window_state_change: frame.window_state_change,
windows,
}
});
RedisplaySignature {
selected_frame,
selected_window,
current_buffer: self.buffers.current_buffer_id().map(|id| id.0),
current_message: self.current_message.clone(),
active_minibuffer_window: self.active_minibuffer_window.map(|id| id.0),
minibuffer_selected_window: self.minibuffer_selected_window.map(|id| id.0),
face_change_count: self.face_change_count,
obarray_function_epoch: self.obarray.function_epoch(),
redisplay_generation: self.redisplay_generation,
frame,
}
}
fn redisplay_buffer_signature(
&self,
id: crate::buffer::BufferId,
) -> Option<RedisplayBufferSignature> {
let buffer = self.buffers.get(id)?;
Some(RedisplayBufferSignature {
id: buffer.id.0,
modified_tick: buffer.modified_tick(),
chars_modified_tick: buffer.chars_modified_tick(),
save_modified_tick: buffer.save_modified_tick(),
autosave_modified_tick: buffer.autosave_modified_tick,
point: buffer.point_char(),
point_byte: buffer.point_byte(),
begv: buffer.begv,
begv_byte: buffer.begv_byte,
zv: buffer.zv,
zv_byte: buffer.zv_byte,
total_chars: buffer.total_chars(),
total_bytes: buffer.total_bytes(),
last_window_start: buffer.last_window_start,
last_selected_window: buffer.last_selected_window.map(|id| id.0),
})
}
fn this_command_name_for_log(&self) -> String {
self.eval_symbol("this-command")
.map(|value| format!("{}", value))
.unwrap_or_else(|_| "<unbound>".to_string())
}
#[tracing::instrument(level = "debug", skip(self))]
pub fn gc_collect(&mut self) {
self.gc_collect_exact();
}
#[tracing::instrument(level = "debug", skip(self))]
pub fn gc_collect_exact(&mut self) {
self.gc_collect_from_current_roots();
}
fn gc_collect_from_current_roots(&mut self) {
let start = std::time::Instant::now();
*self.lexenv_assq_cache.borrow_mut() = LexenvAssqCache::default();
*self.lexenv_special_cache.borrow_mut() = LexenvSpecialCache::default();
let heap_ptr: *mut crate::tagged::gc::TaggedHeap = &mut *self.tagged_heap;
unsafe {
(*heap_ptr).begin_collection();
self.trace_roots(&mut |root| {
(*heap_ptr).seed_root(root);
});
let chain_heads = self.buffers.collect_marker_chain_head_slots();
(*heap_ptr).set_marker_chain_head_slots(chain_heads);
(*heap_ptr).complete_collection();
}
self.gc_pending = false;
self.gc_count += 1;
self.update_gc_runtime_stats(start.elapsed());
self.sync_gc_threshold_from_runtime_settings();
self.run_post_gc_hook();
}
fn with_gc_inhibited<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
self.gc_inhibit_depth += 1;
let result = f(self);
self.gc_inhibit_depth -= 1;
result
}
fn run_post_gc_hook(&mut self) {
let hook = crate::emacs_core::hook_runtime::hook_symbol_by_name(self, "post-gc-hook");
let _ = self.with_gc_inhibited(|eval| {
crate::emacs_core::hook_runtime::safe_run_named_hook(eval, hook, &[])
});
}
pub fn gc_safe_point(&mut self) {
self.gc_safe_point_exact();
}
pub(crate) fn gc_safe_point_exact(&mut self) {
if self.gc_inhibit_depth > 0 {
return;
}
if self.gc_stress || self.gc_pending {
self.gc_collect_from_current_roots();
return;
}
if self.tagged_heap.gc_threshold_is_overridden() {
if self.tagged_heap.should_collect() {
self.gc_collect_from_current_roots();
}
return;
}
if !self.tagged_heap.should_collect() {
return;
}
let threshold = self.effective_gc_threshold_bytes();
if self.tagged_heap.gc_threshold() != threshold {
self.tagged_heap.set_gc_threshold_from_runtime(threshold);
}
if self.tagged_heap.should_collect() {
self.gc_collect_from_current_roots();
}
}
fn process_quit_flag(&mut self) -> Result<(), Flow> {
let flag = self.quit_flag;
self.set_quit_flag_value(Value::NIL);
let throw_on_input = self
.obarray
.symbol_value_id_or_nil(self.throw_on_input_symbol);
if flag
.as_symbol_id()
.map_or(false, |sym| sym == self.kill_emacs_symbol)
{
self.request_shutdown(0, false);
return Err(signal("quit", vec![]));
}
if !throw_on_input.is_nil() && equal_value(&flag, &throw_on_input, 0) {
tracing::debug!(
target: "neomacs::throw_on_input",
?flag,
?throw_on_input,
condition_stack_len = self.condition_stack.len(),
specpdl_len = self.specpdl.len(),
has_matching_catch = self.has_active_catch(&throw_on_input),
"process_quit_flag: throwing for pending input"
);
return Err(Flow::Throw {
tag: throw_on_input,
value: Value::T,
});
}
Err(signal("quit", vec![]))
}
pub(crate) fn maybe_quit(&mut self) -> Result<(), Flow> {
if self
.quit_requested
.load(std::sync::atomic::Ordering::Relaxed)
&& self
.quit_requested
.swap(false, std::sync::atomic::Ordering::Relaxed)
{
if self.quit_flag.is_nil() {
self.set_quit_flag_value(Value::T);
}
}
let quit_flag = self.quit_flag;
if quit_flag.is_nil() {
return Ok(());
}
if self.inhibit_quit.is_truthy() {
return Ok(());
}
self.process_quit_flag()
}
#[inline(always)]
pub(crate) fn quit_flag_value(&self) -> Value {
self.quit_flag
}
#[inline(always)]
pub(crate) fn set_quit_flag_value(&mut self, value: Value) {
self.quit_flag = value;
self.obarray
.set_symbol_value_id(self.quit_flag_symbol, value);
}
pub(crate) fn quit_char(&self) -> i64 {
self.quit_char
}
pub(crate) fn set_quit_char(&mut self, quit: i64) {
self.quit_char = quit & 0o377;
}
pub(crate) fn event_is_quit_char(&self, event: &Value) -> bool {
event
.as_fixnum()
.map_or(false, |code| code == self.quit_char)
}
pub(crate) fn request_quit_from_keyboard_input(&mut self) {
if self.quit_flag_value().is_nil() {
self.set_quit_flag_value(Value::T);
}
}
pub(crate) fn clear_quit_flag_after_read_key_sequence_event(&mut self, event: &Value) {
if !self.event_is_quit_char(event) {
return;
}
let quit_flag = self.quit_flag_value();
if quit_flag.is_nil() {
return;
}
let throw_on_input = self
.obarray
.symbol_value_id_or_nil(self.throw_on_input_symbol);
if equal_value(&quit_flag, &throw_on_input, 0) {
return;
}
self.set_quit_flag_value(Value::NIL);
}
pub(crate) fn input_pending_p_filters_events(&self) -> bool {
self.obarray
.symbol_value("input-pending-p-filter-events")
.copied()
.unwrap_or(Value::T)
.is_truthy()
}
pub(crate) fn track_mouse_enabled(&self) -> bool {
self.obarray
.symbol_value("track-mouse")
.copied()
.unwrap_or(Value::NIL)
.is_truthy()
}
fn should_ignore_while_no_input_event(&self, event: &crate::keyboard::InputEvent) -> bool {
let ignore_symbol = match event {
crate::keyboard::InputEvent::Focus { focused, .. } => {
Some(if *focused { "focus-in" } else { "focus-out" })
}
crate::keyboard::InputEvent::MonitorsChanged { .. } => Some("monitors-changed"),
crate::keyboard::InputEvent::SelectWindow { .. } => Some("select-window"),
_ => None,
};
let Some(ignore_symbol) = ignore_symbol else {
return false;
};
let ignore_list = self
.obarray
.symbol_value("while-no-input-ignore-events")
.copied()
.unwrap_or(Value::NIL);
super::value::list_to_vec(&ignore_list)
.into_iter()
.flatten()
.any(|value| value.is_symbol_named(ignore_symbol))
}
pub(crate) fn input_event_counts_as_pending(
&self,
event: &crate::keyboard::InputEvent,
filter_events: bool,
) -> bool {
match event {
crate::keyboard::InputEvent::Resize { .. } => false,
crate::keyboard::InputEvent::Focus { .. } if !filter_events => false,
crate::keyboard::InputEvent::MouseMove { .. } => self.track_mouse_enabled(),
_ if filter_events && self.should_ignore_while_no_input_event(event) => false,
_ => true,
}
}
fn poll_pending_input_for_throw_on_input(&mut self) -> Result<(), Flow> {
if self.command_loop_noninteractive() && self.input_rx.is_none() {
return Ok(());
}
let throw_on_input = self
.obarray
.symbol_value_id_or_nil(self.throw_on_input_symbol);
if throw_on_input.is_nil() {
return Ok(());
}
if !self.quit_flag.is_nil() {
return Ok(());
}
while self.stage_next_host_input_event_if_available()? {}
if self
.command_loop
.keyboard
.pending_input_events
.iter()
.any(|event| self.input_event_counts_as_pending(event, true))
{
tracing::debug!(
target: "neomacs::throw_on_input",
?throw_on_input,
condition_stack_len = self.condition_stack.len(),
specpdl_len = self.specpdl.len(),
has_matching_catch = self.has_active_catch(&throw_on_input),
pending_input_events = self.command_loop.keyboard.pending_input_events.len(),
"poll_pending_input_for_throw_on_input: setting quit-flag"
);
self.set_quit_flag_value(throw_on_input);
}
Ok(())
}
pub(crate) fn interrupt_for_input_event_if_requested(
&mut self,
event: crate::keyboard::InputEvent,
) -> Result<bool, Flow> {
let throw_on_input = self
.obarray
.symbol_value_id_or_nil(self.throw_on_input_symbol);
if throw_on_input.is_nil() {
return Ok(false);
}
if self.inhibit_quit.is_truthy() {
return Ok(false);
}
self.command_loop
.keyboard
.pending_input_events
.push_front(event);
self.set_quit_flag_value(throw_on_input);
self.maybe_quit()?;
Ok(true)
}
fn maybe_gc_and_quit(&mut self) -> Result<(), Flow> {
self.poll_pending_input_for_throw_on_input()?;
self.maybe_quit()?;
self.gc_safe_point_exact();
Ok(())
}
}
impl Context {
#[inline]
fn maybe_grow_eval_stack<R>(&mut self, callback: impl FnOnce(&mut Self) -> R) -> R {
let depth = self.depth;
if depth < STACK_GROWTH_PROBE_START_DEPTH
|| !depth.is_multiple_of(STACK_GROWTH_PROBE_INTERVAL)
{
return callback(self);
}
stacker::maybe_grow(EVAL_STACK_RED_ZONE, EVAL_STACK_SEGMENT, || callback(self))
}
pub fn lexical_binding(&self) -> bool {
lexenv_is_active(self.lexenv)
}
pub(crate) fn current_input_mode_tuple(&self) -> (bool, bool, bool, i64) {
(self.input_mode_interrupt, false, true, self.quit_char)
}
pub(crate) fn set_input_mode_interrupt(&mut self, interrupt: bool) {
self.input_mode_interrupt = interrupt;
}
#[inline]
fn sync_cached_runtime_binding_by_id(&mut self, sym_id: SymId, value: Value) {
if sym_id == self.quit_flag_symbol {
self.quit_flag = value;
} else if sym_id == self.inhibit_quit_symbol {
self.inhibit_quit = value;
} else if sym_id == self.noninteractive_symbol {
self.noninteractive = value.is_truthy();
} else if sym_id == self.symbols_with_pos_enabled_symbol {
self.symbols_with_pos_enabled = value.is_truthy();
} else if sym_id == self.print_symbols_bare_symbol {
self.print_symbols_bare = value.is_truthy();
}
}
fn sync_keyboard_runtime_binding_by_id(&mut self, sym_id: SymId, value: Value) {
if sym_id == intern("input-decode-map") {
self.command_loop.keyboard.set_input_decode_map(value);
} else if sym_id == intern("local-function-key-map") {
self.command_loop.keyboard.set_local_function_key_map(value);
}
}
pub(crate) fn sync_keyboard_runtime_from_obarray(&mut self) {
let input_decode_map = self
.obarray
.symbol_value("input-decode-map")
.copied()
.unwrap_or(Value::NIL);
let local_function_key_map = self
.obarray
.symbol_value("local-function-key-map")
.copied()
.unwrap_or(Value::NIL);
self.command_loop
.keyboard
.set_terminal_translation_maps(input_decode_map, local_function_key_map);
}
pub(crate) fn waiting_for_user_input(&self) -> bool {
self.waiting_for_user_input
}
pub(crate) fn set_waiting_for_user_input(&mut self, waiting: bool) {
self.waiting_for_user_input = waiting;
}
pub(crate) fn has_input_receiver(&self) -> bool {
self.input_rx.is_some()
}
pub(crate) fn pop_unread_command_event(&mut self) -> Option<Value> {
let event = self.pop_unread_command_event_unrecorded()?;
self.record_input_event(event);
Some(event)
}
pub(crate) fn pop_unread_command_event_unrecorded(&mut self) -> Option<Value> {
let current = match self.eval_symbol("unread-command-events") {
Ok(value) => value,
Err(_) => Value::NIL,
};
match current.kind() {
ValueKind::Cons => {
let mut head = current.cons_car();
let tail = current.cons_cdr();
self.assign("unread-command-events", tail);
if head.is_cons() && head.cons_car() == Value::T {
head = head.cons_cdr();
}
Some(head)
}
_ => None,
}
}
pub(crate) fn peek_unread_command_event(&self) -> Option<Value> {
let current = match self.eval_symbol("unread-command-events") {
Ok(value) => value,
Err(_) => Value::NIL,
};
match current.kind() {
ValueKind::Cons => Some(current.cons_car()),
_ => None,
}
}
pub(crate) fn push_unread_command_event(&mut self, event: Value) {
let current = match self.eval_symbol("unread-command-events") {
Ok(value) => value,
Err(_) => Value::NIL,
};
let new_list = Value::cons(event, current);
self.assign("unread-command-events", new_list);
}
pub(crate) fn replace_unread_command_event_with_singleton(&mut self, event: Value) {
self.assign("unread-command-events", Value::list(vec![event]));
}
pub fn set_lexical_binding(&mut self, enabled: bool) {
self.set_runtime_binding_by_id(intern("lexical-binding"), Value::bool_val(enabled));
if enabled {
if self.lexenv.is_nil() {
self.lexenv = top_level_lexenv_sentinel();
}
} else if is_top_level_lexenv_sentinel(self.lexenv) {
self.lexenv = Value::NIL;
}
}
pub(crate) fn clear_top_level_eval_state(&mut self) {
self.unbind_to(0);
self.lexenv = if lexical_binding_in_obarray(&self.obarray) {
top_level_lexenv_sentinel()
} else {
Value::NIL
};
self.condition_stack.clear();
self.depth = 0;
self.named_call_cache.clear();
}
#[cfg(test)]
pub(crate) fn top_level_eval_state_is_clean(&self) -> bool {
let clean_lexenv = self.lexenv.is_nil()
|| (self.lexical_binding() && is_top_level_lexenv_sentinel(self.lexenv));
self.specpdl.is_empty()
&& clean_lexenv
&& self.vm_root_frames.is_empty()
&& self.condition_stack.is_empty()
&& self.depth == 0
}
#[cfg(test)]
pub(crate) fn condition_stack_depth_for_test(&self) -> usize {
self.condition_stack.len()
}
pub(crate) fn set_interpreted_closure_filter_fn(&mut self, filter_fn: Option<Value>) {
self.interpreted_closure_filter_fn = filter_fn;
if filter_fn.is_none() {
self.interpreted_closure_trim_cache.clear();
self.interpreted_closure_value_cache.clear();
}
}
pub fn load_file_internal(&mut self, path: &std::path::Path) -> EvalResult {
self.load_file_internal_with_flags(path, false, false)
}
pub fn load_file_internal_with_flags(
&mut self,
path: &std::path::Path,
noerror: bool,
nomessage: bool,
) -> EvalResult {
super::load::load_file_with_flags(self, path, noerror, nomessage).map_err(|e| match e {
EvalError::Signal {
symbol,
data,
raw_data,
} => {
if let Some(raw) = raw_data {
signal_with_data(resolve_sym(symbol), raw)
} else {
signal(resolve_sym(symbol), data)
}
}
EvalError::UncaughtThrow { tag, value } => Flow::Throw { tag, value },
})
}
pub(crate) fn eval_value_with_lexical_arg(
&mut self,
form: Value,
lexical_arg: Option<Value>,
) -> EvalResult {
let state = begin_eval_with_lexical_arg_in_state(
&mut self.obarray,
&mut self.lexenv,
&mut self.specpdl,
lexical_arg,
)?;
let eval_result = self.eval_value(&form);
let result = self.dispatch_signal_result_if_needed(eval_result);
finish_eval_with_lexical_arg_in_state(
&mut self.obarray,
&mut self.lexenv,
&mut self.specpdl,
state,
);
result
}
pub(crate) fn eval_lambda_body_value(&mut self, body: Value) -> EvalResult {
self.maybe_grow_eval_stack(|ctx| {
let mut cursor = body;
let mut last = Value::NIL;
while cursor.is_cons() {
last = ctx.eval_sub(cursor.cons_car())?;
cursor = cursor.cons_cdr();
}
Ok(last)
})
}
pub(crate) fn begin_lambda_call(
&mut self,
params: &LambdaParams,
env: Option<Value>,
args: &[Value],
) -> Result<ActiveLambdaCallState, Flow> {
begin_lambda_call_in_state(
&mut self.obarray,
&mut self.specpdl,
&mut self.lexenv,
params,
env,
args,
)
}
pub(crate) fn finish_lambda_call(&mut self, state: ActiveLambdaCallState) {
finish_lambda_call_in_state(
&mut self.obarray,
&mut self.specpdl,
&mut self.lexenv,
state,
);
}
pub(crate) fn sync_features_variable(&mut self) {
sync_features_variable_in_state(&mut self.obarray, &self.features);
}
pub(crate) fn refresh_features_from_variable(&mut self) {
refresh_features_from_variable_in_state(&self.obarray, &mut self.features);
}
fn has_feature(&mut self, name: &str) -> bool {
feature_present_in_state(&self.obarray, &mut self.features, name)
}
pub(crate) fn add_feature(&mut self, name: &str) {
add_feature_in_state(&mut self.obarray, &mut self.features, name);
}
pub(crate) fn feature_present(&mut self, name: &str) -> bool {
self.has_feature(name)
}
pub(crate) fn remove_feature(&mut self, name: &str) {
remove_feature_in_state(&mut self.obarray, &mut self.features, name);
}
pub fn obarray(&self) -> &Obarray {
&self.obarray
}
pub fn obarray_mut(&mut self) -> &mut Obarray {
&mut self.obarray
}
pub fn buffer_manager(&self) -> &BufferManager {
&self.buffers
}
pub fn buffer_manager_mut(&mut self) -> &mut BufferManager {
&mut self.buffers
}
pub fn frame_manager(&self) -> &FrameManager {
&self.frames
}
pub fn frame_manager_mut(&mut self) -> &mut FrameManager {
&mut self.frames
}
pub fn current_message_text(&self) -> Option<String> {
self.current_message
.as_ref()
.map(|message| crate::emacs_core::builtins::runtime_string_from_lisp_string(message))
}
pub fn current_message_value(&self) -> Option<Value> {
self.current_message
.as_ref()
.map(|message| Value::heap_string(message.clone()))
}
pub fn set_current_message(&mut self, message: Option<crate::heap_types::LispString>) {
if self.current_message != message {
self.current_message = message;
self.invalidate_redisplay();
}
}
pub(crate) fn append_current_message_runtime_text(&mut self, text: &str) {
let multibyte = self
.current_message
.as_ref()
.map(crate::heap_types::LispString::is_multibyte)
.unwrap_or(true);
let piece = crate::emacs_core::builtins::runtime_string_to_lisp_string(text, multibyte);
self.append_current_message_lisp_string(&piece);
}
pub(crate) fn append_current_message_lisp_string(
&mut self,
text: &crate::heap_types::LispString,
) {
match self.current_message.as_mut() {
Some(message) => *message = message.concat(text),
None => self.current_message = Some(text.clone()),
}
self.invalidate_redisplay();
}
pub fn clear_current_message(&mut self) {
if self.current_message.is_none() {
return;
}
let hook =
crate::emacs_core::hook_runtime::hook_symbol_by_name(self, "echo-area-clear-hook");
let _ = crate::emacs_core::hook_runtime::safe_run_named_hook(self, hook, &[]);
self.current_message = None;
self.invalidate_redisplay();
}
pub(crate) fn current_message_slot(&mut self) -> &mut Option<crate::heap_types::LispString> {
&mut self.current_message
}
pub(crate) fn sync_keyboard_terminal_owner(&mut self) {
let terminal_id = self
.frames
.selected_frame()
.map(|frame| frame.terminal_id)
.unwrap_or(crate::emacs_core::terminal::pure::TERMINAL_ID);
self.command_loop.keyboard.select_terminal(terminal_id);
}
pub(crate) fn sync_keyboard_terminal_owner_for_input_frame(&mut self, emacs_frame_id: u64) {
let terminal_id = if emacs_frame_id == 0 {
self.frames
.selected_frame()
.map(|frame| frame.terminal_id)
.unwrap_or(crate::emacs_core::terminal::pure::TERMINAL_ID)
} else {
self.frames
.get(crate::window::FrameId(emacs_frame_id))
.map(|frame| frame.terminal_id)
.unwrap_or_else(|| {
self.frames
.selected_frame()
.map(|frame| frame.terminal_id)
.unwrap_or(crate::emacs_core::terminal::pure::TERMINAL_ID)
})
};
self.command_loop.keyboard.select_terminal(terminal_id);
}
pub fn face_table(&self) -> &FaceTable {
&self.face_table
}
pub fn face_table_mut(&mut self) -> &mut FaceTable {
&mut self.face_table
}
pub fn set_face_attribute(
&mut self,
face_name: &str,
attr: &str,
value: crate::face::FaceAttrValue,
) -> bool {
let changed = self.face_table.set_attribute(face_name, attr, value);
if changed {
self.face_change_count += 1;
}
changed
}
pub fn eval_str(&mut self, source: &str) -> Result<Value, EvalError> {
crate::tagged::gc::set_tagged_heap(&mut self.tagged_heap);
let forms = super::value_reader::read_all(source).map_err(|e| EvalError::Signal {
symbol: crate::emacs_core::intern::intern("error"),
data: vec![Value::string(format!("Read error: {}", e.message))],
raw_data: None,
})?;
if forms.is_empty() {
return Ok(Value::NIL);
}
let specpdl_root_scope = self.save_specpdl_roots();
for form in &forms {
self.push_specpdl_root(*form);
}
let mut result = Value::NIL;
let mut error = None;
for form in &forms {
let eval_result = self.eval_sub(*form);
match self.finalize_public_eval_result(eval_result) {
Ok(v) => result = v,
Err(e) => {
error = Some(e);
break;
}
}
}
self.restore_specpdl_roots(specpdl_root_scope);
match error {
Some(e) => Err(e),
None => Ok(result),
}
}
pub fn eval_form(&mut self, form: Value) -> Result<Value, EvalError> {
crate::tagged::gc::set_tagged_heap(&mut self.tagged_heap);
let eval_result = self.eval_sub(form);
self.finalize_public_eval_result(eval_result)
}
fn finalize_public_eval_result(&mut self, result: EvalResult) -> Result<Value, EvalError> {
match result {
Ok(value) => Ok(value),
Err(Flow::Signal(sig)) => match self.dispatch_signal_if_needed(sig) {
Ok(dispatched) => Err(map_flow(Flow::Signal(dispatched))),
Err(flow) => Err(map_flow(flow)),
},
Err(flow) => Err(map_flow(flow)),
}
}
pub fn eval_sub(&mut self, form: Value) -> EvalResult {
let form_unwrapped = self.unwrap_symbol(form);
if let Some(sym_id) = form_unwrapped.as_symbol_id() {
return self.eval_symbol_by_id(sym_id);
}
if !form_unwrapped.is_cons() {
return Ok(form_unwrapped);
}
self.depth += 1;
if self.depth > self.max_depth {
if let Some(v) = self.obarray.symbol_value("max-lisp-eval-depth") {
if let Some(n) = v.as_fixnum() {
let new_max = n.max(100) as usize;
if new_max != self.max_depth {
self.max_depth = new_max;
}
}
}
}
if self.depth > self.max_depth {
let overflow_depth = self.depth as i64;
self.depth -= 1;
return Err(signal(
"excessive-lisp-nesting",
vec![Value::fixnum(overflow_depth)],
));
}
let result = self.maybe_grow_eval_stack(|ctx| {
let specpdl_root_scope = ctx.save_specpdl_roots();
ctx.push_specpdl_root(form);
let result = ctx
.maybe_gc_and_quit()
.and_then(|()| ctx.eval_sub_cons(form));
ctx.restore_specpdl_roots(specpdl_root_scope);
result
});
self.depth -= 1;
result
}
fn eval_sub_cons(&mut self, form: Value) -> EvalResult {
let original_fun = self.unwrap_symbol(form.cons_car());
let original_args = form.cons_cdr();
let outer_bt_count = self.specpdl.len();
self.push_unevalled_backtrace_frame(original_fun, original_args);
let dispatch_result =
self.eval_sub_cons_dispatch(original_fun, original_args, outer_bt_count);
let result = self.dispatch_signal_result_if_needed(dispatch_result);
self.unbind_to_with_result(outer_bt_count, result)
}
fn eval_sub_cons_dispatch(
&mut self,
original_fun: Value,
original_args: Value,
outer_bt_count: usize,
) -> EvalResult {
let sym_id = original_fun.as_symbol_id();
if let Some(sym_id) = sym_id
&& matches!(
sym_id,
id if id == lambda_symbol()
|| id == byte_code_literal_symbol()
|| id == byte_code_symbol()
)
{
if let Some(result) = self.try_special_form_value_id(sym_id, original_args) {
return result;
}
}
let func = if let Some(sym_id) = sym_id {
if let Some(override_func) =
compiler_function_override_in_obarray(&self.obarray, sym_id)
{
override_func
} else {
match self.obarray.symbol_function_id(sym_id) {
Some(f) => {
let mut f = f;
if let Some(alias_id) = f.as_symbol_id() {
if let Some(resolved) = self.obarray.indirect_function_id(alias_id) {
f = resolved;
}
}
if super::autoload::is_autoload_value(&f) {
let _ = super::autoload::builtin_autoload_do_load(
self,
vec![f, Value::from_sym_id(sym_id), Value::NIL],
)?;
match self.obarray.symbol_function_id(sym_id) {
Some(reloaded) => reloaded,
_ => {
return Err(signal(
"void-function",
vec![Value::from_sym_id(sym_id)],
));
}
}
} else {
f
}
}
_ => return Err(signal("void-function", vec![Value::from_sym_id(sym_id)])),
}
}
} else if original_fun.is_cons() {
self.eval_sub(original_fun)?
} else {
return Err(signal("invalid-function", vec![original_fun]));
};
if let Some(surface_sym_id) = sym_id
&& let Some(target_sym_id) = func.as_subr_id()
&& self.subr_is_special_form_id(target_sym_id)
{
let result = if surface_sym_id == target_sym_id {
self.try_special_form_value_id(surface_sym_id, original_args)
} else {
self.try_aliased_special_form_value_id(surface_sym_id, target_sym_id, original_args)
};
if let Some(result) = result {
return result;
}
}
if func.is_macro() {
let arg_values = value_list_to_values(&original_args);
let bt_count = self.specpdl.len();
self.push_backtrace_frame(original_fun, &arg_values);
let expanded =
self.with_macro_expansion_scope(|eval| eval.apply_lambda(func, arg_values.into()));
let expanded = self.unbind_to_with_result(bt_count, expanded);
let expanded = expanded?;
let expanded_root_count = self.specpdl.len();
self.push_specpdl_root(expanded);
let result = self.eval_sub(expanded);
return self.unbind_to_with_result(expanded_root_count, result);
}
if cons_head_symbol_id(&func) == Some(macro_symbol()) {
let macro_fn = func.cons_cdr();
let arg_values = value_list_to_values(&original_args);
let bt_count = self.specpdl.len();
self.push_backtrace_frame(original_fun, &arg_values);
let expanded = self.with_macro_expansion_scope(|eval| eval.apply(macro_fn, arg_values));
let expanded = self.unbind_to_with_result(bt_count, expanded);
let expanded = expanded?;
let expanded_root_count = self.specpdl.len();
self.push_specpdl_root(expanded);
let result = self.eval_sub(expanded);
return self.unbind_to_with_result(expanded_root_count, result);
}
let direct_subr_entry = if let Some((sym_id, entry)) = subr_entry_from_value(func) {
if entry.dispatch_kind != SubrDispatchKind::SpecialForm {
let numargs = match list_length(&original_args) {
Some(n) => n,
None => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("listp"), original_args],
));
}
};
let min = entry.min_args as usize;
let max_ok = match entry.max_args {
Some(m) => numargs <= m as usize,
None => true, };
if numargs < min || !max_ok {
return Err(signal(
"wrong-number-of-arguments",
vec![original_fun, Value::fixnum(numargs as i64)],
));
}
Some((sym_id, entry))
} else {
None
}
} else {
None
};
if !self.function_value_is_callable(&func) {
if func.is_nil() {
return Err(signal("void-function", vec![original_fun]));
}
return Err(signal("invalid-function", vec![original_fun]));
}
let mut args = LispArgVec::new();
self.push_specpdl_root(func);
let args_roots_base = self.specpdl.len();
let mut cursor = original_args;
while cursor.is_cons() {
let arg_form = cursor.cons_car();
let arg_val = self.eval_sub(arg_form)?;
self.push_specpdl_root(arg_val);
args.push(arg_val);
cursor = cursor.cons_cdr();
}
if let Some((sym_id, entry)) = direct_subr_entry {
if Self::subr_entry_uses_fixed_value_call(entry) {
self.set_backtrace_args_evalled_owned(outer_bt_count, args);
self.unbind_to(args_roots_base);
self.maybe_gc_and_quit()?;
return self.maybe_grow_eval_stack(|ctx| {
ctx.dispatch_subr_entry_from_backtrace_unchecked(entry, outer_bt_count)
.unwrap_or_else(|| {
Err(signal("void-function", vec![Value::from_sym_id(sym_id)]))
})
});
}
}
self.set_backtrace_args_evalled(outer_bt_count, &args);
self.unbind_to(args_roots_base);
self.maybe_gc_and_quit()?;
if let Some((sym_id, entry)) = direct_subr_entry {
return self.maybe_grow_eval_stack(|ctx| {
if entry.dispatch_kind == SubrDispatchKind::ContextCallable {
return ctx.apply_evaluator_callable_by_id(sym_id, args);
}
ctx.dispatch_subr_entry_unchecked(entry, args)
.unwrap_or_else(|| {
Err(signal("void-function", vec![Value::from_sym_id(sym_id)]))
})
});
}
self.maybe_grow_eval_stack(|ctx| ctx.funcall_general_untraced(func, args))
}
pub fn eval_value(&mut self, value: &Value) -> EvalResult {
self.eval_sub(*value)
}
pub fn eval_str_each(&mut self, source: &str) -> Vec<Result<Value, EvalError>> {
crate::tagged::gc::set_tagged_heap(&mut self.tagged_heap);
let forms = match super::value_reader::read_all(source) {
Ok(f) => f,
Err(e) => {
return vec![Err(EvalError::Signal {
symbol: intern("error"),
data: vec![Value::string(format!("Read error: {}", e.message))],
raw_data: None,
})];
}
};
let specpdl_root_scope = self.save_specpdl_roots();
for form in &forms {
self.push_specpdl_root(*form);
}
let mut results = Vec::with_capacity(forms.len());
for form in &forms {
let result = self.eval_sub(*form).map_err(map_flow);
if let Ok(ref val) = result {
self.push_specpdl_root(*val);
}
results.push(result);
}
self.restore_specpdl_roots(specpdl_root_scope);
results
}
pub fn set_variable(&mut self, name: &str, value: Value) {
let sym_id = intern(name);
self.note_macro_expansion_mutation();
use super::symbol::SymbolRedirect;
if let Some(sym) = self.obarray.get_by_id(sym_id)
&& sym.flags.redirect() == SymbolRedirect::Forwarded
&& let Some(buf_id) = self.buffers.current_buffer_id()
{
use super::forward::{LispBufferObjFwd, LispFwdType};
let fwd_ptr = unsafe { sym.val.fwd };
let header = unsafe { &*fwd_ptr };
if matches!(header.ty, LispFwdType::BufferObj) {
let buf_fwd = unsafe { &*(fwd_ptr as *const LispBufferObjFwd) };
let offset = buf_fwd.offset as usize;
if let Some(buf) = self.buffers.get_mut(buf_id)
&& offset < buf.slots.len()
{
buf.slots[offset] = value;
self.refresh_gc_runtime_settings_after_change_by_id(sym_id);
return;
}
}
}
self.obarray.set_symbol_value(name, value);
self.sync_cached_runtime_binding_by_id(sym_id, value);
self.refresh_gc_runtime_settings_after_change_by_id(sym_id);
}
#[inline]
pub(crate) fn noninteractive(&self) -> bool {
self.noninteractive
}
#[inline]
pub fn unwrap_symbol(&self, val: Value) -> Value {
if self.symbols_with_pos_enabled && val.is_symbol_with_pos() {
val.as_symbol_with_pos_sym().unwrap()
} else {
val
}
}
pub(crate) fn sync_thread_runtime_bindings(&mut self) {
if let Some(main_thread) = self.threads.thread_handle(0) {
self.obarray.set_symbol_value("main-thread", main_thread);
}
}
pub fn set_function(&mut self, name: &str, value: Value) {
self.note_macro_expansion_mutation();
self.obarray.set_symbol_function(name, value);
}
#[inline]
fn forwarded_buffer_obj_value(
&self,
sym: &crate::emacs_core::symbol::LispSymbol,
) -> Option<Value> {
use crate::emacs_core::forward::{LispBufferObjFwd, LispFwdType};
let fwd = unsafe { &*sym.val.fwd };
if !matches!(fwd.ty, LispFwdType::BufferObj) {
return None;
}
let buf_fwd = unsafe { &*(fwd as *const _ as *const LispBufferObjFwd) };
let off = buf_fwd.offset as usize;
if let Some(buf) = self.buffers.current_buffer() {
let local = buf_fwd.local_flags_idx < 0 || buf.slot_local_flag(off);
if local && off < buf.slots.len() {
return Some(buf.slots[off]);
}
}
if off < self.buffers.buffer_defaults.len() {
Some(self.buffers.buffer_defaults[off])
} else {
Some(buf_fwd.default)
}
}
pub(crate) fn set_buffer_local_binding_by_id(
&mut self,
buffer_id: crate::buffer::BufferId,
sym_id: SymId,
value: Value,
) -> Result<(), Flow> {
let resolved = builtins::resolve_variable_alias_id_in_obarray(&self.obarray, sym_id)?;
if crate::buffer::buffer::lookup_buffer_slot_by_sym_id(resolved).is_some()
|| resolved == buffer_undo_list_symbol()
{
let _ = self
.buffers
.set_buffer_local_property_by_sym_id(buffer_id, resolved, value);
return Ok(());
}
if !self.obarray.get_by_id(resolved).is_some_and(|sym| {
sym.redirect() == crate::emacs_core::symbol::SymbolRedirect::Localized
}) {
let default = self
.obarray
.find_symbol_value(resolved)
.unwrap_or(Value::UNBOUND);
self.obarray.make_symbol_localized(resolved, default);
}
let _ = self
.buffers
.set_buffer_local_property_by_sym_id(buffer_id, resolved, value);
let target_buf = Value::make_buffer(buffer_id);
let alist = self
.buffers
.get(buffer_id)
.map(|buf| buf.local_var_alist)
.unwrap_or(Value::NIL);
let _ = self.obarray.find_symbol_value_in_buffer(
resolved,
Some(buffer_id),
target_buf,
alist,
None,
0,
None,
);
Ok(())
}
pub(crate) fn eval_symbol_by_id(&self, sym_id: SymId) -> EvalResult {
if is_keyword_id(sym_id) {
return Ok(Value::from_kw_id(sym_id));
}
if self.lexical_binding() {
if let Some(value) = self.lexenv_lookup_cached_in(self.lexenv, sym_id) {
return Ok(value);
}
}
let resolved = super::builtins::resolve_variable_alias_id(self, sym_id)?;
let resolved_is_canonical = is_canonical_id(resolved);
if resolved != sym_id && self.lexical_binding() {
if let Some(value) = self.lexenv_lookup_cached_in(self.lexenv, resolved) {
return Ok(value);
}
}
if is_keyword_id(resolved) {
return Ok(Value::from_kw_id(resolved));
}
use crate::emacs_core::symbol::SymbolRedirect;
if let Some(sym) = self.obarray.get_by_id(resolved) {
match sym.redirect() {
SymbolRedirect::Localized if resolved_is_canonical => {
if let Some(buf) = self.buffers.current_buffer() {
let target_buf = Value::make_buffer(buf.id);
if let Some(value) =
self.obarray
.read_localized(resolved, target_buf, buf.local_var_alist)
{
if value.is_unbound() {
return Err(signal(
"void-variable",
vec![value_from_symbol_id(sym_id)],
));
}
return Ok(value);
}
}
}
SymbolRedirect::Forwarded => {
if let Some(value) = self.forwarded_buffer_obj_value(sym) {
return Ok(value);
}
}
SymbolRedirect::Plainval | SymbolRedirect::Varalias | SymbolRedirect::Localized => {
}
}
}
if resolved_is_canonical
&& resolved == buffer_undo_list_symbol()
&& let Some(buf) = self.buffers.current_buffer()
&& let Some(binding) = buf.get_buffer_local_binding_by_sym_id(resolved)
{
return binding
.as_value()
.ok_or_else(|| signal("void-variable", vec![value_from_symbol_id(sym_id)]));
}
if let Some(value) = self.obarray.find_symbol_value(resolved) {
return Ok(value);
}
if is_canonical_id(sym_id) && sym_id == nil_symbol() {
return Ok(Value::NIL);
}
if is_canonical_id(sym_id) && sym_id == t_symbol() {
return Ok(Value::T);
}
if resolved_is_canonical && resolved == nil_symbol() {
return Ok(Value::NIL);
}
if resolved_is_canonical && resolved == t_symbol() {
return Ok(Value::T);
}
Err(signal("void-variable", vec![value_from_symbol_id(sym_id)]))
}
pub(crate) fn eval_symbol(&self, symbol: &str) -> EvalResult {
self.eval_symbol_by_id(intern(symbol))
}
fn apply_symbol_callable(
&mut self,
sym_id: SymId,
args: LispArgVec,
rewrite_builtin_wrong_arity: bool,
) -> EvalResult {
if super::builtins::is_canonical_symbol_id(sym_id) {
let invalid_fn = if self.subr_is_special_form_id(sym_id) {
Value::subr_from_sym_id(sym_id)
} else {
value_from_symbol_id(sym_id)
};
return self.apply_named_callable_by_id(
sym_id,
args,
invalid_fn,
rewrite_builtin_wrong_arity,
);
}
if self.obarray.is_function_unbound_id(sym_id) {
return Err(signal("void-function", vec![Value::from_sym_id(sym_id)]));
}
let Some(function) = self.obarray.symbol_function_id(sym_id) else {
return Err(signal("void-function", vec![Value::from_sym_id(sym_id)]));
};
if super::autoload::is_autoload_value(&function) {
let name = resolve_sym(sym_id);
return self.apply_named_autoload_callable(
name,
function,
args,
rewrite_builtin_wrong_arity,
);
}
let function_is_callable = self.function_value_is_callable(&function);
let result = self.apply_untraced(function, args);
match &result {
Err(Flow::Signal(sig))
if sig.symbol_name() == "invalid-function" && !function_is_callable =>
{
Err(signal("invalid-function", vec![Value::from_sym_id(sym_id)]))
}
_ => result,
}
}
fn apply_symbol_callable_untraced(
&mut self,
sym_id: SymId,
args: LispArgVec,
rewrite_builtin_wrong_arity: bool,
) -> EvalResult {
if super::builtins::is_canonical_symbol_id(sym_id) {
let invalid_fn = if self.subr_is_special_form_id(sym_id) {
Value::subr_from_sym_id(sym_id)
} else {
value_from_symbol_id(sym_id)
};
return self.apply_named_callable_by_id_core(
sym_id,
args,
invalid_fn,
rewrite_builtin_wrong_arity,
);
}
if self.obarray.is_function_unbound_id(sym_id) {
return Err(signal("void-function", vec![Value::from_sym_id(sym_id)]));
}
let Some(function) = self.obarray.symbol_function_id(sym_id) else {
return Err(signal("void-function", vec![Value::from_sym_id(sym_id)]));
};
if super::autoload::is_autoload_value(&function) {
let name = resolve_sym(sym_id);
return self.apply_named_autoload_callable(
name,
function,
args,
rewrite_builtin_wrong_arity,
);
}
let function_is_callable = self.function_value_is_callable(&function);
let result = self.apply_untraced(function, args);
match &result {
Err(Flow::Signal(sig))
if sig.symbol_name() == "invalid-function" && !function_is_callable =>
{
Err(signal("invalid-function", vec![Value::from_sym_id(sym_id)]))
}
_ => result,
}
}
pub(crate) fn function_value_is_callable(&self, function: &Value) -> bool {
match function.kind() {
ValueKind::Veclike(VecLikeType::Lambda)
| ValueKind::Veclike(VecLikeType::ByteCode)
| ValueKind::Veclike(VecLikeType::Macro) => true,
ValueKind::Subr(_) | ValueKind::Veclike(VecLikeType::Subr) => {
super::subr_info::subr_is_callable_function_value(function)
}
ValueKind::Cons => {
super::autoload::is_autoload_value(function)
|| matches!(
cons_head_symbol_id(function),
Some(id) if is_lambda_like_symbol_id(id) || id == macro_symbol()
)
}
ValueKind::Symbol(id) => {
super::builtins::symbols::resolve_indirect_symbol_by_id(self, id)
.is_some_and(|(_, resolved)| self.function_value_is_callable(&resolved))
}
_ => false,
}
}
fn maybe_writeback_mutating_first_arg(
&mut self,
called_name: &str,
alias_target: Option<&str>,
call_args: &[Value],
result: &Value,
) {
let mutates_fillarray =
called_name == "fillarray" || alias_target.is_some_and(|name| name == "fillarray");
let mutates_aset = called_name == "aset" || alias_target.is_some_and(|name| name == "aset");
if !mutates_fillarray && !mutates_aset {
return;
}
let Some(first_arg) = call_args.first() else {
return;
};
if !first_arg.is_string() {
return;
}
let replacement = if mutates_fillarray {
if !result.is_string() || eq_value(first_arg, result) {
return;
}
*result
} else {
if call_args.len() < 3 {
return;
}
let Ok(updated) =
super::builtins::aset_string_replacement(first_arg, &call_args[1], &call_args[2])
else {
return;
};
if eq_value(first_arg, &updated) {
return;
}
updated
};
if crate::emacs_core::value::equal_value(first_arg, &replacement, 0) {
return;
}
let mut visited = HashSet::new();
{
let mut lexenv_val = self.lexenv;
Self::replace_alias_refs_in_value(
&mut lexenv_val,
first_arg,
&replacement,
&mut visited,
);
self.lexenv = lexenv_val;
}
if let Some(current_id) = self.buffers.current_buffer_id()
&& let Some(buf) = self.buffers.get_mut(current_id)
{
for value in buf.bound_buffer_local_values_mut() {
Self::replace_alias_refs_in_value(value, first_arg, &replacement, &mut visited);
}
}
self.obarray.for_each_value_cell_mut(|value| {
Self::replace_alias_refs_in_value(value, first_arg, &replacement, &mut visited);
});
}
fn replace_alias_refs_in_value(
value: &mut Value,
from: &Value,
to: &Value,
visited: &mut HashSet<usize>,
) {
if eq_value(value, from) {
*value = *to;
return;
}
match value.kind() {
ValueKind::Cons => {
let key = value.bits() ^ 0x1;
if !visited.insert(key) {
return;
}
let mut new_car = value.cons_car();
let mut new_cdr = value.cons_cdr();
Self::replace_alias_refs_in_value(&mut new_car, from, to, visited);
Self::replace_alias_refs_in_value(&mut new_cdr, from, to, visited);
value.set_car(new_car);
value.set_cdr(new_cdr);
}
ValueKind::Veclike(VecLikeType::Vector) => {
let key = value.bits() ^ 0x2;
if !visited.insert(key) {
return;
}
let mut values = value.as_vector_data().unwrap().clone();
for item in values.iter_mut() {
Self::replace_alias_refs_in_value(item, from, to, visited);
}
let _ = value.replace_vector_data(values);
}
ValueKind::Veclike(VecLikeType::Record) => {
let key = value.bits() ^ 0x2;
if !visited.insert(key) {
return;
}
let mut values = value.as_record_data().unwrap().clone();
for item in values.iter_mut() {
Self::replace_alias_refs_in_value(item, from, to, visited);
}
let _ = value.replace_record_data(values);
}
ValueKind::Veclike(VecLikeType::HashTable) => {
let key = value.bits() ^ 0x4;
if !visited.insert(key) {
return;
}
let mut ht = value.as_hash_table().unwrap().clone();
let old_ptr = if from.is_string() {
Some(from.bits())
} else {
None
};
let new_ptr = if to.is_string() {
Some(to.bits())
} else {
None
};
if matches!(ht.test, HashTableTest::Eq | HashTableTest::Eql) {
if let (Some(old_ptr), Some(new_ptr)) = (old_ptr, new_ptr) {
if let Some(existing) = ht.data.remove(&HashKey::Ptr(old_ptr)) {
ht.data.insert(HashKey::Ptr(new_ptr), existing);
}
if ht.key_snapshots.remove(&HashKey::Ptr(old_ptr)).is_some() {
ht.key_snapshots.insert(HashKey::Ptr(new_ptr), *to);
}
for k in &mut ht.insertion_order {
if *k == HashKey::Ptr(old_ptr) {
*k = HashKey::Ptr(new_ptr);
}
}
}
}
for item in ht.data.values_mut() {
Self::replace_alias_refs_in_value(item, from, to, visited);
}
let _ = value.replace_hash_table(ht);
}
_ => {}
}
}
fn try_special_form_value_id(&mut self, sym_id: SymId, tail: Value) -> Option<EvalResult> {
let saved_depth = self.depth;
let result = self.try_special_form_inner_value_id(sym_id, tail);
self.depth = saved_depth;
result
}
fn try_aliased_special_form_value_id(
&mut self,
surface_id: SymId,
target_id: SymId,
tail: Value,
) -> Option<EvalResult> {
let saved_depth = self.depth;
let surface_name = resolve_sym(surface_id);
let result = Some(match target_id {
id if id == quote_symbol() => self.sf_quote_value_named(surface_name, tail),
id if id == function_symbol() => self.sf_function_value_named(surface_name, tail),
id if id == let_symbol() => self.sf_let_value_named(surface_name, tail),
id if id == let_star_symbol() => self.sf_let_star_value_named(surface_name, tail),
id if id == setq_symbol() => self.sf_setq_value_named(surface_name, tail),
id if id == if_symbol() => self.sf_if_value_named(surface_name, tail),
id if id == and_symbol() => self.sf_and_value(tail),
id if id == or_symbol() => self.sf_or_value(tail),
id if id == cond_symbol() => self.sf_cond_value(tail),
id if id == while_symbol() => self.sf_while_value_named(surface_name, tail),
id if id == progn_symbol() => self.sf_progn_value(tail),
id if id == prog1_symbol() => self.sf_prog1_value_named(surface_name, tail),
id if id == defvar_symbol() => self.sf_defvar_value_named(surface_name, tail),
id if id == defconst_symbol() => self.sf_defconst_value_named(surface_name, tail),
id if id == catch_symbol() => self.sf_catch_value_named(surface_name, tail),
id if id == unwind_protect_symbol() => {
self.sf_unwind_protect_value_named(surface_name, tail)
}
id if id == condition_case_symbol() => {
self.sf_condition_case_value_named(surface_name, tail)
}
id if id == save_excursion_symbol() => self.sf_save_excursion_value(tail),
id if id == save_current_buffer_symbol() => self.sf_save_current_buffer_value(tail),
id if id == save_restriction_symbol() => self.sf_save_restriction_value(tail),
id if id == interactive_symbol_id() => Ok(Value::NIL),
_ => return None,
});
self.depth = saved_depth;
result
}
fn try_special_form_inner_value_id(
&mut self,
sym_id: SymId,
tail: Value,
) -> Option<EvalResult> {
Some(match sym_id {
id if id == quote_symbol() => self.sf_quote_value(tail),
id if id == function_symbol() => self.sf_function_value(tail),
id if id == let_symbol() => self.sf_let_value(tail),
id if id == let_star_symbol() => self.sf_let_star_value(tail),
id if id == setq_symbol() => self.sf_setq_value(tail),
id if id == if_symbol() => self.sf_if_value(tail),
id if id == and_symbol() => self.sf_and_value(tail),
id if id == or_symbol() => self.sf_or_value(tail),
id if id == cond_symbol() => self.sf_cond_value(tail),
id if id == while_symbol() => self.sf_while_value(tail),
id if id == progn_symbol() => self.sf_progn_value(tail),
id if id == prog1_symbol() => self.sf_prog1_value(tail),
id if id == defvar_symbol() => self.sf_defvar_value(tail),
id if id == defconst_symbol() => self.sf_defconst_value(tail),
id if id == catch_symbol() => self.sf_catch_value(tail),
id if id == unwind_protect_symbol() => self.sf_unwind_protect_value(tail),
id if id == condition_case_symbol() => self.sf_condition_case_value(tail),
id if id == save_excursion_symbol() => self.sf_save_excursion_value(tail),
id if id == save_current_buffer_symbol() => self.sf_save_current_buffer_value(tail),
id if id == save_restriction_symbol() => self.sf_save_restriction_value(tail),
id if id == interactive_symbol_id() => Ok(Value::NIL),
id if id == lambda_symbol() => self.sf_lambda_value(tail),
id if id == byte_code_literal_symbol() => self.sf_byte_code_literal_value(tail),
id if id == byte_code_symbol() => self.sf_byte_code_value(tail),
_ => return None,
})
}
fn listp_error(&self, value: Value) -> Flow {
let mut tail = value;
while tail.is_cons() {
tail = tail.cons_cdr();
}
signal("wrong-type-argument", vec![Value::symbol("listp"), tail])
}
fn value_list_len_or_error(&self, list: Value) -> Result<usize, Flow> {
list_length(&list).ok_or_else(|| self.listp_error(list))
}
fn one_unevalled_arg(&self, name: &str, tail: Value) -> Result<Value, Flow> {
let mut cursor = tail;
if !cursor.is_cons() {
return if cursor.is_nil() {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(0)],
))
} else {
Err(self.listp_error(tail))
};
}
let arg = cursor.cons_car();
cursor = cursor.cons_cdr();
if !cursor.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::symbol(name),
Value::fixnum(self.value_list_len_or_error(tail)? as i64),
],
));
}
Ok(arg)
}
fn sf_quote_value(&mut self, tail: Value) -> EvalResult {
self.sf_quote_value_named("quote", tail)
}
fn sf_quote_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
Ok(self.one_unevalled_arg(call_name, tail)?)
}
fn sf_function_value(&mut self, tail: Value) -> EvalResult {
self.sf_function_value_named("function", tail)
}
fn sf_function_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
let arg = self.one_unevalled_arg(call_name, tail)?;
if cons_head_symbol_id(&arg) == Some(lambda_symbol()) {
return self.instantiate_callable_cons_form(arg);
}
Ok(arg)
}
fn sf_lambda_value(&mut self, tail: Value) -> EvalResult {
self.instantiate_callable_cons_form(Value::cons(Value::from_sym_id(lambda_symbol()), tail))
}
fn sf_let_value(&mut self, tail: Value) -> EvalResult {
self.sf_let_value_named("let", tail)
}
fn sf_let_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
if tail.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(0)],
));
}
if !tail.is_cons() {
return Err(self.listp_error(tail));
}
let varlist = tail.cons_car();
let body = tail.cons_cdr();
let mut lexical_bindings = LetBindingVec::new();
let mut dynamic_sym_ids = LetBindingVec::new();
let use_lexical = self.lexical_binding();
let mut constant_binding_error: Option<String> = None;
let specpdl_root_scope = self.save_specpdl_roots();
let mut bindings = varlist;
while bindings.is_cons() {
let binding = self.unwrap_symbol(bindings.cons_car());
bindings = bindings.cons_cdr();
if let Some(id) = binding.as_symbol_id() {
if let Some(name) = symbol_sets_constant_error(id) {
if constant_binding_error.is_none() {
constant_binding_error = Some(name.to_owned());
}
continue;
}
if use_lexical
&& !self.obarray.is_special_id(id)
&& !self.lexenv_declares_special_cached_in(self.lexenv, id)
{
lexical_bindings.push((id, Value::NIL));
} else {
dynamic_sym_ids.push((id, Value::NIL));
}
continue;
}
if !binding.is_cons() {
self.restore_specpdl_roots(specpdl_root_scope);
return Err(signal("wrong-type-argument", vec![]));
}
let head = self.unwrap_symbol(binding.cons_car());
let Some(id) = head.as_symbol_id() else {
self.restore_specpdl_roots(specpdl_root_scope);
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), head],
));
};
let mut value_tail = binding.cons_cdr();
let value = if value_tail.is_nil() {
Value::NIL
} else if value_tail.is_cons() {
let init_form = value_tail.cons_car();
value_tail = value_tail.cons_cdr();
if !value_tail.is_nil() {
self.restore_specpdl_roots(specpdl_root_scope);
return Err(signal(
"error",
vec![
Value::string("`let' bindings can have only one value-form"),
binding,
],
));
}
match self.eval_sub(init_form) {
Ok(value) => value,
Err(err) => {
self.restore_specpdl_roots(specpdl_root_scope);
return Err(err);
}
}
} else {
self.restore_specpdl_roots(specpdl_root_scope);
return Err(self.listp_error(binding));
};
self.push_specpdl_root(value);
if let Some(name) = symbol_sets_constant_error(id) {
if constant_binding_error.is_none() {
constant_binding_error = Some(name.to_owned());
}
continue;
}
if use_lexical
&& !self.obarray.is_special_id(id)
&& !self.lexenv_declares_special_cached_in(self.lexenv, id)
{
lexical_bindings.push((id, value));
} else {
dynamic_sym_ids.push((id, value));
}
}
if !bindings.is_nil() {
self.restore_specpdl_roots(specpdl_root_scope);
return Err(self.listp_error(varlist));
}
if let Some(name) = constant_binding_error {
self.restore_specpdl_roots(specpdl_root_scope);
return Err(signal("setting-constant", vec![Value::symbol(name)]));
}
self.restore_specpdl_roots(specpdl_root_scope);
let lexenv_at_entry = self.lexenv;
let specpdl_count = self.specpdl.len();
if use_lexical {
self.specpdl.push(SpecBinding::LexicalEnv {
old_lexenv: lexenv_at_entry,
});
}
let mut new_lexenv = lexenv_at_entry;
for (sym_id, val) in &lexical_bindings {
let binding_pair = Value::make_cons(
crate::emacs_core::eval::lexenv_binding_symbol_value(*sym_id),
*val,
);
self.specpdl.push(SpecBinding::GcRoot {
value: binding_pair,
});
new_lexenv = Value::make_cons(binding_pair, new_lexenv);
match self.specpdl.last_mut() {
Some(SpecBinding::GcRoot { value }) => *value = new_lexenv,
_ => unreachable!(),
}
}
self.lexenv = new_lexenv;
for (sym_id, value) in &dynamic_sym_ids {
self.specbind(*sym_id, *value);
}
let result = self.sf_progn_value(body);
self.unbind_to(specpdl_count);
result
}
fn sf_let_star_value(&mut self, tail: Value) -> EvalResult {
self.sf_let_star_value_named("let*", tail)
}
fn sf_let_star_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
if tail.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(0)],
));
}
if !tail.is_cons() {
return Err(self.listp_error(tail));
}
let varlist = tail.cons_car();
let body = tail.cons_cdr();
let use_lexical = self.lexical_binding();
let specpdl_count = self.specpdl.len();
if use_lexical {
self.specpdl.push(SpecBinding::LexicalEnv {
old_lexenv: self.lexenv,
});
}
let init_result: Result<(), Flow> = (|| {
let mut bindings = varlist;
while bindings.is_cons() {
let binding = self.unwrap_symbol(bindings.cons_car());
bindings = bindings.cons_cdr();
let (id, value) = if let Some(id) = binding.as_symbol_id() {
(id, Value::NIL)
} else if binding.is_cons() {
let head = self.unwrap_symbol(binding.cons_car());
let Some(id) = head.as_symbol_id() else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), head],
));
};
let mut value_tail = binding.cons_cdr();
let value = if value_tail.is_nil() {
Value::NIL
} else if value_tail.is_cons() {
let init_form = value_tail.cons_car();
value_tail = value_tail.cons_cdr();
if !value_tail.is_nil() {
return Err(signal(
"error",
vec![
Value::string("`let' bindings can have only one value-form"),
binding,
],
));
}
self.eval_sub(init_form)?
} else {
return Err(self.listp_error(binding));
};
(id, value)
} else {
return Err(signal("wrong-type-argument", vec![]));
};
if let Some(name) = symbol_sets_constant_error(id) {
return Err(signal("setting-constant", vec![Value::symbol(name)]));
}
if use_lexical
&& !self.obarray.is_special_id(id)
&& !self.lexenv_declares_special_cached_in(self.lexenv, id)
{
let binding = Value::make_cons(lexenv_binding_symbol_value(id), value);
self.lexenv = Value::make_cons(binding, self.lexenv);
} else {
self.specbind(id, value);
}
}
if !bindings.is_nil() {
return Err(self.listp_error(varlist));
}
Ok(())
})();
if let Err(error) = init_result {
self.unbind_to(specpdl_count);
return Err(error);
}
let result = self.sf_progn_value(body);
self.unbind_to(specpdl_count);
result
}
fn sf_setq_value(&mut self, tail: Value) -> EvalResult {
self.sf_setq_value_named("setq", tail)
}
fn sf_setq_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
if tail.is_nil() {
return Ok(Value::NIL);
}
let mut cursor = tail;
let mut last = Value::NIL;
let mut nargs: usize = 0;
while cursor.is_cons() {
let symbol = cursor.cons_car();
cursor = cursor.cons_cdr();
nargs += 1;
if cursor.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(nargs as i64)],
));
}
if !cursor.is_cons() {
return Err(self.listp_error(tail));
}
let value_form = cursor.cons_car();
cursor = cursor.cons_cdr();
nargs += 1;
let symbol = self.unwrap_symbol(symbol);
let Some(sym_id) = symbol.as_symbol_id() else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), symbol],
));
};
let value = self.eval_sub(value_form)?;
let resolved_id = super::builtins::resolve_variable_alias_id(self, sym_id)?;
if self.obarray.is_constant_id(resolved_id)
&& !self.has_local_binding_by_id(sym_id)
&& (resolved_id == sym_id || !self.has_local_binding_by_id(resolved_id))
{
if let Some(result) = super::builtins::constant_set_outcome_in_obarray(
self.obarray(),
resolved_id,
value_from_symbol_id(sym_id),
value,
) {
return result;
}
}
if sym_id == default_directory_symbol()
&& value.is_string()
&& value.string_is_multibyte()
{
tracing::debug!(
"SETQ default-directory to MULTIBYTE string: {:?}",
runtime_string_value(value),
);
}
self.assign_with_watchers_by_id(resolved_id, value, "set")?;
last = value;
}
if !cursor.is_nil() {
return Err(self.listp_error(tail));
}
Ok(last)
}
fn sf_if_value(&mut self, tail: Value) -> EvalResult {
self.sf_if_value_named("if", tail)
}
fn sf_if_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
if tail.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(0)],
));
}
if !tail.is_cons() {
return Err(self.listp_error(tail));
}
let cond_form = tail.cons_car();
let mut rest = tail.cons_cdr();
if rest.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(1)],
));
}
if !rest.is_cons() {
return Err(self.listp_error(tail));
}
let then_form = rest.cons_car();
rest = rest.cons_cdr();
if self.eval_sub(cond_form)?.is_truthy() {
self.eval_sub(then_form)
} else {
self.sf_progn_value(rest)
}
}
fn sf_and_value(&mut self, tail: Value) -> EvalResult {
let mut cursor = tail;
let mut last = Value::T;
while cursor.is_cons() {
last = self.eval_sub(cursor.cons_car())?;
if last.is_nil() {
return Ok(Value::NIL);
}
cursor = cursor.cons_cdr();
}
if !cursor.is_nil() {
return Err(self.listp_error(tail));
}
Ok(last)
}
fn sf_or_value(&mut self, tail: Value) -> EvalResult {
let mut cursor = tail;
while cursor.is_cons() {
let value = self.eval_sub(cursor.cons_car())?;
if value.is_truthy() {
return Ok(value);
}
cursor = cursor.cons_cdr();
}
if !cursor.is_nil() {
return Err(self.listp_error(tail));
}
Ok(Value::NIL)
}
fn sf_cond_value(&mut self, tail: Value) -> EvalResult {
let mut clauses = tail;
while clauses.is_cons() {
let clause = clauses.cons_car();
clauses = clauses.cons_cdr();
if clause.is_nil() {
continue;
}
if !clause.is_cons() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("listp"), clause],
));
}
let test = clause.cons_car();
let body = clause.cons_cdr();
let test_value = self.eval_sub(test)?;
if test_value.is_truthy() {
if body.is_nil() {
return Ok(test_value);
}
return self.sf_progn_value(body);
}
}
if !clauses.is_nil() {
return Err(self.listp_error(tail));
}
Ok(Value::NIL)
}
fn sf_while_value(&mut self, tail: Value) -> EvalResult {
self.sf_while_value_named("while", tail)
}
fn sf_while_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
if tail.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(0)],
));
}
if !tail.is_cons() {
return Err(self.listp_error(tail));
}
let test_form = tail.cons_car();
let body = tail.cons_cdr();
let mut iters: u64 = 0;
loop {
if self.eval_sub(test_form)?.is_nil() {
return Ok(Value::NIL);
}
self.sf_progn_value(body)?;
iters += 1;
if iters == 1_000_000 {
let cond_str = super::print::print_value(&test_form);
tracing::warn!(
"while loop exceeded 1M iterations, cond: {}",
&cond_str[..cond_str.len().min(300)]
);
}
self.maybe_quit()?;
}
}
fn sf_progn_value(&mut self, forms: Value) -> EvalResult {
let mut cursor = forms;
let mut last = Value::NIL;
while cursor.is_cons() {
last = self.eval_sub(cursor.cons_car())?;
cursor = cursor.cons_cdr();
}
if !cursor.is_nil() {
return Err(self.listp_error(forms));
}
Ok(last)
}
fn sf_prog1_value(&mut self, tail: Value) -> EvalResult {
self.sf_prog1_value_named("prog1", tail)
}
fn sf_prog1_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
if tail.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(0)],
));
}
if !tail.is_cons() {
return Err(self.listp_error(tail));
}
let first_form = tail.cons_car();
let rest = tail.cons_cdr();
let first = self.eval_sub(first_form)?;
let specpdl_root_scope = self.save_specpdl_roots();
self.push_specpdl_root(first);
let result = self.sf_progn_value(rest);
self.restore_specpdl_roots(specpdl_root_scope);
result?;
Ok(first)
}
fn sf_defvar_value(&mut self, tail: Value) -> EvalResult {
self.sf_defvar_value_named("defvar", tail)
}
fn sf_defvar_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
if tail.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(0)],
));
}
if !tail.is_cons() {
return Err(self.listp_error(tail));
}
let symbol = self.unwrap_symbol(tail.cons_car());
let Some(sym_id) = symbol.as_symbol_id() else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), symbol],
));
};
let mut rest = tail.cons_cdr();
if rest.is_nil() {
if self.lexical_binding()
&& !self.lexenv.is_nil()
&& !self.obarray.is_special_id(sym_id)
{
self.lexenv = Value::cons(Value::from_sym_id(sym_id), self.lexenv);
}
return Ok(Value::from_sym_id(sym_id));
}
if !rest.is_cons() {
return Err(self.listp_error(tail));
}
let init_form = rest.cons_car();
rest = rest.cons_cdr();
let documentation = if rest.is_nil() {
Value::NIL
} else if rest.is_cons() {
let doc = rest.cons_car();
rest = rest.cons_cdr();
if !rest.is_nil() {
return Err(signal("error", vec![Value::string("Too many arguments")]));
}
doc
} else {
return Err(self.listp_error(tail));
};
let mut define_args = vec![symbol];
if !documentation.is_nil() {
define_args.push(documentation);
}
super::builtins::symbols::builtin_internal_define_uninitialized_variable(
self,
define_args,
)?;
let was_bound = default_toplevel_value_in_state(
&self.obarray,
self.specpdl.as_slice(),
Some(&self.buffers.buffer_defaults),
sym_id,
)
.is_some()
|| self.obarray.is_constant_id(sym_id);
if !was_bound {
let value = self.eval_sub(init_form)?;
super::builtins::symbols::builtin_set_default_toplevel_value(
self,
vec![symbol, value],
)?;
}
Ok(Value::from_sym_id(sym_id))
}
fn sf_defconst_value(&mut self, tail: Value) -> EvalResult {
self.sf_defconst_value_named("defconst", tail)
}
fn sf_defconst_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
if tail.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(0)],
));
}
if !tail.is_cons() {
return Err(self.listp_error(tail));
}
let symbol = self.unwrap_symbol(tail.cons_car());
let Some(sym_id) = symbol.as_symbol_id() else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), symbol],
));
};
let mut rest = tail.cons_cdr();
if rest.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(1)],
));
}
if !rest.is_cons() {
return Err(self.listp_error(tail));
}
let init_form = rest.cons_car();
rest = rest.cons_cdr();
let documentation = if rest.is_nil() {
Value::NIL
} else if rest.is_cons() {
let doc = rest.cons_car();
rest = rest.cons_cdr();
if !rest.is_nil() {
return Err(signal("error", vec![Value::string("Too many arguments")]));
}
doc
} else {
return Err(self.listp_error(tail));
};
let mut define_args = vec![symbol];
if !documentation.is_nil() {
define_args.push(documentation);
}
super::builtins::symbols::builtin_internal_define_uninitialized_variable(
self,
define_args,
)?;
let value = self.eval_sub(init_form)?;
super::custom::builtin_set_default(self, vec![symbol, value])?;
self.obarray.make_special_id(sym_id);
self.obarray
.put_property_id(sym_id, intern("risky-local-variable"), Value::T)?;
Ok(Value::from_sym_id(sym_id))
}
fn sf_catch_value(&mut self, tail: Value) -> EvalResult {
self.sf_catch_value_named("catch", tail)
}
fn sf_catch_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
if tail.is_nil() {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(0)],
));
}
if !tail.is_cons() {
return Err(self.listp_error(tail));
}
let tag = self.eval_sub(tail.cons_car())?;
self.push_condition_frame(ConditionFrame::Catch {
tag,
resume: ResumeTarget::InterpreterCatch,
});
let result = match self.sf_progn_value(tail.cons_cdr()) {
Ok(value) => Ok(value),
Err(Flow::Signal(sig)) => match self.dispatch_signal_if_needed(sig) {
Ok(dispatched) => Err(Flow::Signal(dispatched)),
Err(Flow::Throw {
tag: thrown_tag,
value,
}) if eq_value(&tag, &thrown_tag) => Ok(value),
Err(flow) => Err(flow),
},
Err(Flow::Throw {
tag: thrown_tag,
value,
}) if eq_value(&tag, &thrown_tag) => Ok(value),
Err(flow) => Err(flow),
};
self.pop_condition_frame();
result
}
fn sf_unwind_protect_value(&mut self, tail: Value) -> EvalResult {
self.sf_unwind_protect_value_named("unwind-protect", tail)
}
fn sf_unwind_protect_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
let nargs = self.value_list_len_or_error(tail)?;
if nargs < 1 {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(nargs as i64)],
));
}
let body = tail.cons_car();
let cleanup_forms = tail.cons_cdr();
let root_slot = self.specpdl.len();
self.specpdl.push(SpecBinding::GcRoot { value: Value::NIL });
self.specpdl.push(SpecBinding::UnwindProtect {
forms: cleanup_forms,
lexenv: self.lexenv,
});
let result = self.eval_sub(body);
if let Ok(v) = result {
if let Some(SpecBinding::GcRoot { value }) = self.specpdl.get_mut(root_slot) {
*value = v;
}
}
self.unbind_to(root_slot);
result
}
fn sf_condition_case_value(&mut self, tail: Value) -> EvalResult {
self.sf_condition_case_value_named("condition-case", tail)
}
fn sf_condition_case_value_named(&mut self, call_name: &str, tail: Value) -> EvalResult {
let nargs = self.value_list_len_or_error(tail)?;
if nargs < 3 {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(call_name), Value::fixnum(nargs as i64)],
));
}
let var = self.unwrap_symbol(tail.cons_car());
let Some(var_id) = var.as_symbol_id() else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), var],
));
};
let rest = tail.cons_cdr();
if !rest.is_cons() {
return Err(self.listp_error(tail));
}
let body = rest.cons_car();
let handlers = rest.cons_cdr();
let mut handlers_vec = Vec::new();
let mut success_handler_idx: Option<usize> = None;
let mut cursor = handlers;
while cursor.is_cons() {
let handler = cursor.cons_car();
let handler_index = handlers_vec.len();
handlers_vec.push(handler);
cursor = cursor.cons_cdr();
if handler.is_nil() {
continue;
}
if !handler.is_cons() {
return Err(signal(
"error",
vec![Value::string(format!(
"Invalid condition handler: {}",
super::print::print_value(&handler)
))],
));
}
let head = handler.cons_car();
if !(head.is_symbol() || head.is_symbol_with_pos() || head.is_cons()) {
return Err(signal(
"error",
vec![Value::string(format!(
"Invalid condition handler: {}",
super::print::print_value(&handler)
))],
));
}
let head_unwrapped = self.unwrap_symbol(head);
if head_unwrapped.is_symbol_named(":success") {
success_handler_idx = Some(handler_index);
}
}
if !cursor.is_nil() {
return Err(self.listp_error(handlers));
}
let condition_stack_base = self.condition_stack_len();
for (idx, handler) in handlers_vec.iter().enumerate().rev() {
if success_handler_idx == Some(idx) || handler.is_nil() {
continue;
}
if !handler.is_cons() {
continue;
}
let conditions = handler.cons_car();
self.push_condition_frame(ConditionFrame::ConditionCase {
conditions,
resume: ResumeTarget::InterpreterConditionCase {
handler_index: idx,
condition_stack_base,
},
});
}
match self.eval_sub(body) {
Ok(value) => {
self.truncate_condition_stack(condition_stack_base);
if let Some(idx) = success_handler_idx {
let handler = handlers_vec[idx];
let bind_var = !var.is_nil();
let specpdl_count = self.specpdl.len();
if bind_var {
self.specbind(var_id, value);
}
let result = self.sf_progn_value(handler.cons_cdr());
self.unbind_to(specpdl_count);
return result;
}
Ok(value)
}
Err(Flow::Signal(sig)) => {
let sig = match self.dispatch_signal_if_needed(sig) {
Ok(dispatched) => dispatched,
Err(flow) => {
self.truncate_condition_stack(condition_stack_base);
return Err(flow);
}
};
self.truncate_condition_stack(condition_stack_base);
if let Some(ResumeTarget::InterpreterConditionCase {
handler_index,
condition_stack_base: selected_stack_base,
}) = sig.selected_resume.clone()
&& selected_stack_base == condition_stack_base
{
let handler = handlers_vec[handler_index];
let bind_var = !var.is_nil();
let binding_value = make_signal_binding_value(&sig);
let use_lexical_binding = bind_var
&& self.lexical_binding()
&& !is_runtime_dynamically_special(&self.obarray, var_id)
&& !self.lexenv_declares_special_cached_in(self.lexenv, var_id);
let specpdl_count = self.specpdl.len();
if use_lexical_binding {
self.specpdl.push(SpecBinding::LexicalEnv {
old_lexenv: self.lexenv,
});
let binding =
Value::make_cons(lexenv_binding_symbol_value(var_id), binding_value);
self.lexenv = Value::make_cons(binding, self.lexenv);
} else if bind_var {
self.specbind(var_id, binding_value);
}
let result = self.sf_progn_value(handler.cons_cdr());
self.unbind_to(specpdl_count);
return result;
}
Err(Flow::Signal(sig))
}
Err(flow @ Flow::Throw { .. }) => {
self.truncate_condition_stack(condition_stack_base);
Err(flow)
}
}
}
fn sf_save_excursion_value(&mut self, tail: Value) -> EvalResult {
let count = self.specpdl.len();
self.record_save_excursion();
let result = self.sf_progn_value(tail);
self.unbind_to(count);
result
}
fn sf_save_current_buffer_value(&mut self, tail: Value) -> EvalResult {
let saved_buf = self.buffers.current_buffer().map(|b| b.id);
let result = self.sf_progn_value(tail);
if let Some(saved_id) = saved_buf {
self.restore_current_buffer_if_live(saved_id);
}
result
}
fn sf_save_restriction_value(&mut self, tail: Value) -> EvalResult {
let count = self.specpdl.len();
if let Some(state) = self.buffers.save_current_restriction_state() {
self.specpdl.push(SpecBinding::SaveRestriction { state });
}
let result = self.sf_progn_value(tail);
self.unbind_to(count);
result
}
fn validate_throw(&self, flow: Flow) -> Flow {
match flow {
Flow::Throw { ref tag, ref value } => {
if self.has_active_catch(tag) {
flow
} else {
signal("no-catch", vec![*tag, *value])
}
}
other => other,
}
}
fn quote_value_with_bytecode(&mut self, value: Value) -> EvalResult {
if value.is_cons() && cons_head_symbol_id(&value) == Some(byte_code_literal_symbol()) {
return self.sf_byte_code_literal_value(value.cons_cdr());
}
match value.kind() {
ValueKind::Veclike(VecLikeType::Vector) => {
let items = value.as_vector_data().unwrap();
let mut values = Vec::with_capacity(items.len());
for item in items {
values.push(self.quote_value_with_bytecode(*item)?);
}
Ok(Value::vector(values))
}
_ => Ok(value),
}
}
fn sf_byte_code_literal_value(&mut self, tail: Value) -> EvalResult {
let vector = self.one_unevalled_arg("byte-code-literal", tail)?;
let Some(items) = vector.as_vector_data() else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("vectorp"), vector],
));
};
if items.len() < 4 {
return Ok(vector);
}
let mut values = Vec::with_capacity(items.len());
for item in items {
values.push(self.quote_value_with_bytecode(*item)?);
}
crate::emacs_core::builtins::make_byte_code_from_slots(&values)
}
fn sf_byte_code_value(&mut self, tail: Value) -> EvalResult {
let args = list_to_vec(&tail).ok_or_else(|| self.listp_error(tail))?;
if args.len() != 3 {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol("byte-code"), Value::fixnum(args.len() as i64)],
));
}
let trace_toplevel_bytecode = std::env::var_os("NEOVM_TRACE_TOPLEVEL_BYTECODE").is_some();
let load_file_name = if trace_toplevel_bytecode {
self.obarray()
.symbol_value("load-file-name")
.and_then(|value| value.as_runtime_string_owned())
.unwrap_or_else(|| "<unknown>".to_string())
} else {
String::new()
};
let decode_start = trace_toplevel_bytecode.then(std::time::Instant::now);
let bytecode_str = args[0];
let constants_vec = self.quote_value_with_bytecode(args[1])?;
let maxdepth = args[2];
use crate::emacs_core::bytecode::ByteCodeFunction;
use crate::emacs_core::bytecode::decode::{
decode_gnu_bytecode_with_offset_map, string_value_to_bytes,
};
use crate::emacs_core::value::LambdaParams;
let raw_bytes = if let Some(ls) = bytecode_str.as_lisp_string() {
ls.as_bytes().to_vec()
} else {
Vec::new()
};
let mut constants: Vec<Value> = match constants_vec.kind() {
ValueKind::Veclike(VecLikeType::Vector) => {
constants_vec.as_vector_data().unwrap().clone()
}
_ => Vec::new(),
};
for i in 0..constants.len() {
constants[i] =
crate::emacs_core::builtins::try_convert_nested_compiled_literal(constants[i]);
}
let (ops, gnu_byte_offset_map) =
decode_gnu_bytecode_with_offset_map(&raw_bytes, &mut constants).map_err(|e| {
signal(
"error",
vec![Value::string(format!("bytecode decode error: {}", e))],
)
})?;
if let Some(start) = decode_start {
tracing::info!(
"TOPLEVEL-BYTECODE decode file={} bytes={} consts={} ops={} elapsed={:.2?}",
load_file_name,
raw_bytes.len(),
constants.len(),
ops.len(),
start.elapsed()
);
}
let max_stack = match maxdepth.kind() {
ValueKind::Fixnum(n) => n as u16,
_ => 16,
};
let bc = ByteCodeFunction {
ops,
constants,
max_stack,
params: LambdaParams::simple(vec![]),
arglist: Value::NIL,
lexical: false,
env: None,
gnu_byte_offset_map: Some(gnu_byte_offset_map),
gnu_bytecode_bytes: None,
docstring: None,
doc_form: None,
interactive: None,
closure_slot_count: 4,
extra_slots: Vec::new(),
};
let mut vm = super::bytecode::Vm::from_context(self);
let exec_start = trace_toplevel_bytecode.then(std::time::Instant::now);
let result = vm.execute(&bc, vec![]);
if let Some(start) = exec_start {
tracing::info!(
"TOPLEVEL-BYTECODE exec file={} ops={} elapsed={:.2?}",
load_file_name,
bc.ops.len(),
start.elapsed()
);
}
result
}
pub(crate) fn defalias_value(&mut self, sym: Value, def: Value) -> EvalResult {
let plan = builtins::plan_defalias_in_obarray(self.obarray(), &[sym, def])?;
let builtins::DefaliasPlan { action, result, .. } = plan;
self.loadhist_attach(Value::cons(Value::symbol("defun"), result));
match action {
builtins::DefaliasAction::SetFunction { symbol, definition } => {
self.note_macro_expansion_mutation();
self.obarray.set_symbol_function_id(symbol, definition);
}
builtins::DefaliasAction::CallHook {
hook,
symbol_value,
definition,
} => {
self.apply(hook, vec![symbol_value, definition])?;
}
}
if let Some(symbol) = result.as_symbol_id() {
let definition = self
.obarray
.symbol_function_id(symbol)
.unwrap_or(Value::NIL);
crate::emacs_core::interactive::sync_interactive_registry_for_symbol_definition(
&mut self.interactive,
symbol,
definition,
);
}
Ok(result)
}
fn current_load_list_is_file_context(current_load_list: Value) -> bool {
let mut tail = current_load_list;
while tail.is_cons() {
if tail.cons_cdr().is_nil() && tail.cons_car().is_string() {
return true;
}
tail = tail.cons_cdr();
}
false
}
pub(crate) fn loadhist_attach(&mut self, entry: Value) {
self.loadhist_attach_inner(entry, false);
}
pub(crate) fn loadhist_attach_once(&mut self, entry: Value) {
self.loadhist_attach_inner(entry, true);
}
fn loadhist_attach_inner(&mut self, entry: Value, dedup: bool) {
let current_load_list = self.visible_variable_value_or_nil("current-load-list");
if !Self::current_load_list_is_file_context(current_load_list) {
return;
}
if dedup {
let mut cursor = current_load_list;
while cursor.is_cons() {
if equal_value(&cursor.cons_car(), &entry, 0) {
return;
}
cursor = cursor.cons_cdr();
}
}
let roots = self.save_specpdl_roots();
self.push_specpdl_root(current_load_list);
self.push_specpdl_root(entry);
self.set_variable("current-load-list", Value::cons(entry, current_load_list));
self.restore_specpdl_roots(roots);
}
#[tracing::instrument(level = "info", skip(self, subfeatures))]
pub(crate) fn provide_value(
&mut self,
feature: Value,
subfeatures: Option<Value>,
) -> EvalResult {
self.note_macro_expansion_mutation();
provide_value_in_state(&mut self.obarray, &mut self.features, feature, subfeatures)?;
self.loadhist_attach(Value::cons(Value::symbol("provide"), feature));
self.run_after_load_hooks_for_feature(feature)?;
Ok(feature)
}
fn run_after_load_hooks_for_feature(&mut self, feature: Value) -> Result<(), Flow> {
let after_load_alist = self
.obarray
.symbol_value("after-load-alist")
.cloned()
.unwrap_or(Value::NIL);
if after_load_alist.is_nil() {
return Ok(());
}
let entry = {
let mut cursor = after_load_alist;
let mut found = Value::NIL;
while cursor.is_cons() {
let pair_car = cursor.cons_car();
let pair_cdr = cursor.cons_cdr();
if pair_car.is_cons() {
let inner_pair_car = pair_car.cons_car();
if inner_pair_car == feature {
found = pair_car;
break;
}
}
cursor = pair_cdr;
}
found
};
if entry.is_nil() {
return Ok(());
}
let callbacks = entry.cons_cdr();
let mut cursor = callbacks;
while cursor.is_cons() {
let pair_car = cursor.cons_car();
let pair_cdr = cursor.cons_cdr();
let callback = pair_car;
self.apply(callback, vec![])?;
cursor = pair_cdr;
}
Ok(())
}
#[tracing::instrument(level = "info", skip(self), err(Debug))]
pub(crate) fn require_value(
&mut self,
feature: Value,
filename: Option<Value>,
noerror: Option<Value>,
) -> EvalResult {
let feature_name =
super::builtins::symbols::symbol_id(&feature).map(|sid| resolve_sym(sid).to_string());
let filename_str = filename.as_ref().and_then(|v| v.as_runtime_string_owned());
match plan_require_in_state(
&self.obarray,
&mut self.features,
&self.require_stack,
feature,
filename.clone(),
noerror.clone(),
) {
Err(e) => {
tracing::error!(
feature = ?feature_name,
filename = ?filename_str,
"require plan failed: {:?}", e
);
return Err(e);
}
Ok(plan) => {
self.loadhist_attach_once(Value::cons(Value::symbol("require"), feature));
match plan {
RequirePlan::Return(value) => Ok(value),
RequirePlan::Load { sym_id, name, path } => {
self.require_stack.push(sym_id);
let result = (|| -> EvalResult {
self.load_file_internal(&path)?;
self.refresh_features_from_variable();
finish_require_in_state(&self.features, sym_id, &name)
})();
let _ = self.require_stack.pop();
if let Err(ref e) = result {
let noerror_val =
noerror.as_ref().map(|v| !v.is_nil()).unwrap_or(false);
let path_str = path.display().to_string();
tracing::error!(
feature_name = ?feature_name,
path = %path_str,
noerror = noerror_val,
"require failed: {:?}", e
);
}
result
}
}
}
}
}
fn maybe_use_cached_interpreted_closure_filter(
&mut self,
closure_hook: Value,
params_value: Value,
body_value: Value,
env_value: Value,
docstring_value: Value,
iform_value: Value,
) -> Option<EvalResult> {
let Some(hook_sym) = closure_hook.as_symbol_id() else {
return None;
};
if hook_sym != cconv_make_interpreted_closure_symbol() {
return None;
}
let Some(expected_fn) = self.interpreted_closure_filter_fn else {
return None;
};
let Some(current_fn) = self
.obarray
.symbol_function_id(cconv_make_interpreted_closure_symbol())
else {
return None;
};
if !eq_value(¤t_fn, &expected_fn) {
return None;
}
let env_shape = interpreted_closure_env_entries(env_value);
let cache_fp =
interpreted_closure_trim_fingerprint(params_value, body_value, iform_value, &env_shape);
let entry = self
.interpreted_closure_trim_cache
.get(&cache_fp)?
.iter()
.find(|entry| entry.matches(params_value, body_value, iform_value, &env_shape))?
.clone();
let rebuilt_env =
rebuild_trimmed_interpreted_closure_env(env_value, &entry.trimmed_env_template);
Some(builtins::symbols::make_interpreted_closure_from_parts(
&entry.trimmed_params_value,
&entry.trimmed_body_value,
&rebuilt_env,
Some(&docstring_value),
Some(&iform_value),
))
}
fn maybe_cache_interpreted_closure_filter_result(
&mut self,
closure_hook: Value,
params_value: Value,
body_value: Value,
env_value: Value,
iform_value: Value,
result: &Value,
) {
let Some(hook_sym) = closure_hook.as_symbol_id() else {
return;
};
if hook_sym != cconv_make_interpreted_closure_symbol() {
return;
}
let Some(expected_fn) = self.interpreted_closure_filter_fn else {
return;
};
let Some(current_fn) = self
.obarray
.symbol_function_id(cconv_make_interpreted_closure_symbol())
else {
return;
};
if !eq_value(¤t_fn, &expected_fn) {
return;
}
if !result.is_lambda() {
return;
};
let Some(trimmed_params_value) = result.closure_slot(CLOSURE_ARGLIST) else {
return;
};
let Some(trimmed_body_value) = result.closure_body_value() else {
return;
};
let Some(trimmed_env) = result.closure_env().flatten() else {
return;
};
let env_shape = interpreted_closure_env_entries(env_value);
let cache_fp =
interpreted_closure_trim_fingerprint(params_value, body_value, iform_value, &env_shape);
let bucket = self
.interpreted_closure_trim_cache
.entry(cache_fp)
.or_default();
if bucket
.iter()
.any(|entry| entry.matches(params_value, body_value, iform_value, &env_shape))
{
return;
}
bucket.push(InterpretedClosureTrimCacheEntry {
params_value,
body_value,
iform_value,
env_shape,
trimmed_params_value,
trimmed_body_value,
trimmed_env_template: interpreted_closure_env_entries(trimmed_env),
});
}
fn maybe_use_cached_value_interpreted_closure_filter(
&mut self,
closure_hook: Value,
source_function: Value,
env_value: Value,
docstring_value: Value,
iform_value: Value,
) -> Option<EvalResult> {
let Some(hook_sym) = closure_hook.as_symbol_id() else {
return None;
};
if hook_sym != cconv_make_interpreted_closure_symbol() {
return None;
}
let Some(expected_fn) = self.interpreted_closure_filter_fn else {
return None;
};
let Some(current_fn) = self
.obarray
.symbol_function_id(cconv_make_interpreted_closure_symbol())
else {
return None;
};
if !eq_value(¤t_fn, &expected_fn) {
return None;
}
let env_shape = interpreted_closure_env_entries(env_value);
let cache_key = (
runtime_tail_fingerprint(&[source_function]),
interpreted_closure_env_shape_hash(&env_shape),
);
let entry = self
.interpreted_closure_value_cache
.get(&cache_key)?
.iter()
.find(|entry| entry.matches(source_function, &env_shape))?
.clone();
let rebuilt_env =
rebuild_trimmed_interpreted_closure_env(env_value, &entry.trimmed_env_template);
Some(builtins::symbols::make_interpreted_closure_from_parts(
&entry.trimmed_params_value,
&entry.trimmed_body_value,
&rebuilt_env,
Some(&docstring_value),
Some(&iform_value),
))
}
fn maybe_cache_value_interpreted_closure_filter_result(
&mut self,
closure_hook: Value,
source_function: Value,
env_value: Value,
result: &Value,
) {
let Some(hook_sym) = closure_hook.as_symbol_id() else {
return;
};
if hook_sym != cconv_make_interpreted_closure_symbol() {
return;
}
let Some(expected_fn) = self.interpreted_closure_filter_fn else {
return;
};
let Some(current_fn) = self
.obarray
.symbol_function_id(cconv_make_interpreted_closure_symbol())
else {
return;
};
if !eq_value(¤t_fn, &expected_fn) {
return;
}
if !result.is_lambda() {
return;
};
let Some(trimmed_params_value) = result.closure_slot(CLOSURE_ARGLIST) else {
return;
};
let Some(trimmed_body_value) = result.closure_body_value() else {
return;
};
let Some(trimmed_env) = result.closure_env().flatten() else {
return;
};
let env_shape = interpreted_closure_env_entries(env_value);
let cache_key = (
runtime_tail_fingerprint(&[source_function]),
interpreted_closure_env_shape_hash(&env_shape),
);
let bucket = self
.interpreted_closure_value_cache
.entry(cache_key)
.or_default();
if bucket
.iter()
.any(|entry| entry.matches(source_function, &env_shape))
{
return;
}
bucket.push(InterpretedClosureValueCacheEntry {
source_function,
env_shape,
trimmed_params_value,
trimmed_body_value,
trimmed_env_template: interpreted_closure_env_entries(trimmed_env),
});
}
fn make_interpreted_closure_with_expr_runtime_hook(
&mut self,
params_value: Value,
body_value: Value,
env_value: Value,
docstring_value: Value,
iform_value: Value,
) -> EvalResult {
if !env_value.is_nil() {
let closure_hook = self.visible_variable_value_or_nil_by_id(
internal_make_interpreted_closure_function_symbol(),
);
if !closure_hook.is_nil() {
if let Some(cached) = self.maybe_use_cached_interpreted_closure_filter(
closure_hook,
params_value,
body_value,
env_value,
docstring_value,
iform_value,
) {
return cached;
}
let result = self.apply(
closure_hook,
vec![
params_value,
body_value,
env_value,
docstring_value,
iform_value,
],
);
if let Ok(value) = &result {
self.maybe_cache_interpreted_closure_filter_result(
closure_hook,
params_value,
body_value,
env_value,
iform_value,
value,
);
}
return result;
}
}
builtins::symbols::make_interpreted_closure_from_parts(
¶ms_value,
&body_value,
&env_value,
Some(&docstring_value),
Some(&iform_value),
)
}
fn make_interpreted_closure_with_value_runtime_hook(
&mut self,
source_function: Value,
params_value: Value,
body_value: Value,
env_value: Value,
docstring_value: Value,
iform_value: Value,
) -> EvalResult {
if !env_value.is_nil() {
let closure_hook = self.visible_variable_value_or_nil_by_id(
internal_make_interpreted_closure_function_symbol(),
);
if !closure_hook.is_nil() {
if let Some(cached) = self.maybe_use_cached_value_interpreted_closure_filter(
closure_hook,
source_function,
env_value,
docstring_value,
iform_value,
) {
return cached;
}
let result = self.apply(
closure_hook,
vec![
params_value,
body_value,
env_value,
docstring_value,
iform_value,
],
);
if let Ok(value) = &result {
self.maybe_cache_value_interpreted_closure_filter_result(
closure_hook,
source_function,
env_value,
value,
);
}
return result;
}
}
builtins::symbols::make_interpreted_closure_from_parts(
¶ms_value,
&body_value,
&env_value,
Some(&docstring_value),
Some(&iform_value),
)
}
fn eval_dynamic_documentation_value(&mut self, value: Value) -> Result<Option<Value>, Flow> {
if !value.is_cons() || value.cons_car().as_symbol_name() != Some(":documentation") {
return Ok(None);
}
let tail = value.cons_cdr();
if tail.is_nil() {
return Ok(Some(Value::NIL));
}
if !tail.is_cons() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("listp"), value],
));
}
self.eval_value(&tail.cons_car()).map(Some)
}
pub(crate) fn push_backtrace_frame(&mut self, function: Value, args: &[Value]) {
self.specpdl.push(SpecBinding::Backtrace {
function,
args: BacktraceArgs::Evaluated(args.iter().copied().collect()),
debug_on_exit: false,
});
}
pub(crate) fn push_unevalled_backtrace_frame(&mut self, function: Value, original_args: Value) {
self.specpdl.push(SpecBinding::Backtrace {
function,
args: BacktraceArgs::Unevalled(original_args),
debug_on_exit: false,
});
}
pub(crate) fn set_backtrace_args_evalled(&mut self, count: usize, evaluated: &[Value]) {
let entry = self
.specpdl
.get_mut(count)
.expect("set_backtrace_args_evalled: specpdl index out of range");
match entry {
SpecBinding::Backtrace {
args: backtrace_args,
..
} if backtrace_args.is_unevalled() => {
*backtrace_args = BacktraceArgs::Evaluated(evaluated.iter().copied().collect());
}
other => panic!(
"set_backtrace_args_evalled: expected UNEVALLED Backtrace at specpdl[{count}], got {other:?}"
),
}
}
pub(crate) fn set_backtrace_args_evalled_owned(&mut self, count: usize, evaluated: LispArgVec) {
let entry = self
.specpdl
.get_mut(count)
.expect("set_backtrace_args_evalled_owned: specpdl index out of range");
match entry {
SpecBinding::Backtrace {
args: backtrace_args,
..
} if backtrace_args.is_unevalled() => {
*backtrace_args = BacktraceArgs::Evaluated(evaluated);
}
other => panic!(
"set_backtrace_args_evalled_owned: expected UNEVALLED Backtrace at specpdl[{count}], got {other:?}"
),
}
}
pub(crate) fn save_specpdl_roots(&self) -> SpecpdlRootScopeState {
SpecpdlRootScopeState {
saved_len: self.specpdl.len(),
}
}
pub(crate) fn push_specpdl_root(&mut self, value: Value) {
self.specpdl.push(SpecBinding::GcRoot { value });
}
pub(crate) fn record_save_excursion(&mut self) -> Option<usize> {
let (buffer_id, point) = self
.buffers
.current_buffer()
.map(|buffer| (buffer.id, buffer.point_char() as i64 + 1))?;
let marker = super::marker::make_registered_buffer_marker(
&mut self.buffers,
buffer_id,
point,
false,
);
let marker_id = super::marker::marker_id_value(&marker)
.expect("registered save-excursion marker should carry an id");
let count = self.specpdl.len();
self.specpdl.push(SpecBinding::SaveExcursion {
buffer_id,
marker_id,
marker,
});
Some(count)
}
pub(crate) fn restore_specpdl_roots(&mut self, scope: SpecpdlRootScopeState) {
if self.specpdl.len() <= scope.saved_len {
return;
}
if self.specpdl[scope.saved_len..]
.iter()
.all(|binding| matches!(binding, SpecBinding::GcRoot { .. }))
{
self.specpdl.truncate(scope.saved_len);
return;
}
let mut index = 0usize;
self.specpdl.retain(|binding| {
let keep = index < scope.saved_len || !matches!(binding, SpecBinding::GcRoot { .. });
index += 1;
keep
});
}
pub(crate) fn push_vm_root_frame(&mut self) {
self.vm_root_frames.push(VmRootFrame::new());
}
pub(crate) fn pop_vm_root_frame(&mut self) {
self.vm_root_frames.pop();
}
pub(crate) fn push_vm_frame_root(&mut self, value: Value) {
self.vm_root_frames
.last_mut()
.expect("VM root frame missing")
.roots
.push(value);
}
pub(crate) fn push_eval_result_roots(&mut self, result: &EvalResult) {
match result {
Ok(value) => self.push_vm_frame_root(*value),
Err(Flow::Signal(sig)) => {
for value in sig.data.iter().copied() {
self.push_vm_frame_root(value);
}
if let Some(raw_data) = sig.raw_data {
self.push_vm_frame_root(raw_data);
}
}
Err(Flow::Throw { tag, value }) => {
self.push_vm_frame_root(*tag);
self.push_vm_frame_root(*value);
}
}
}
pub(crate) fn save_vm_roots(&mut self) -> VmRootScopeState {
let pushed_vm_root_frame = self.vm_root_frames.is_empty();
if pushed_vm_root_frame {
self.push_vm_root_frame();
}
VmRootScopeState {
pushed_vm_root_frame,
saved_vm_root_frame_len: self.vm_root_frames.last().map(|frame| frame.roots.len()),
}
}
pub(crate) fn save_vm_frame_roots(&self) -> usize {
self.vm_root_frames
.last()
.expect("VM root frame missing")
.roots
.len()
}
pub(crate) fn restore_vm_frame_roots(&mut self, saved_len: usize) {
self.vm_root_frames
.last_mut()
.expect("VM root frame missing")
.roots
.truncate(saved_len);
}
pub(crate) fn restore_vm_roots(&mut self, scope: VmRootScopeState) {
if let Some(saved_len) = scope.saved_vm_root_frame_len {
self.restore_vm_frame_roots(saved_len);
}
if scope.pushed_vm_root_frame {
self.pop_vm_root_frame();
}
}
pub(crate) fn unbind_to_with_result(&mut self, count: usize, result: EvalResult) -> EvalResult {
let root_scope = self.save_vm_roots();
self.push_eval_result_roots(&result);
self.unbind_to(count);
self.restore_vm_roots(root_scope);
result
}
fn apply_internal(
&mut self,
function: Value,
args: LispArgVec,
record_backtrace: bool,
) -> EvalResult {
let bt_count = self.specpdl.len();
if record_backtrace {
self.push_backtrace_frame(function, &args);
}
let result = self.maybe_gc_and_quit().and_then(|_| {
self.maybe_grow_eval_stack(|ctx| ctx.funcall_general_untraced(function, args))
});
let result = self.dispatch_signal_result_if_needed(result);
self.unbind_to_with_result(bt_count, result)
}
pub(crate) fn apply<A>(&mut self, function: Value, args: A) -> EvalResult
where
A: Into<LispArgVec>,
{
self.apply_internal(function, args.into(), true)
}
#[inline]
pub(crate) fn apply0(&mut self, function: Value) -> EvalResult {
self.apply(function, LispArgVec::new())
}
#[inline]
pub(crate) fn apply1(&mut self, function: Value, arg0: Value) -> EvalResult {
let mut args = LispArgVec::new();
args.push(arg0);
self.apply(function, args)
}
#[inline]
pub(crate) fn apply2(&mut self, function: Value, arg0: Value, arg1: Value) -> EvalResult {
let mut args = LispArgVec::new();
args.push(arg0);
args.push(arg1);
self.apply(function, args)
}
pub(crate) fn apply_untraced<A>(&mut self, function: Value, args: A) -> EvalResult
where
A: Into<LispArgVec>,
{
self.apply_internal(function, args.into(), false)
}
pub(crate) fn apply_with_frame_function(
&mut self,
frame_function: Value,
func: Value,
args: impl Into<LispArgVec>,
) -> EvalResult {
let args = args.into();
let bt_count = self.specpdl.len();
self.push_backtrace_frame(frame_function, &args);
let result = self.maybe_gc_and_quit().and_then(|_| {
self.maybe_grow_eval_stack(|ctx| ctx.funcall_general_untraced(func, args))
});
let result = self.dispatch_signal_result_if_needed(result);
self.unbind_to_with_result(bt_count, result)
}
pub(crate) fn funcall_general<A>(&mut self, function: Value, args: A) -> EvalResult
where
A: Into<LispArgVec>,
{
let args = args.into();
let bt_count = self.specpdl.len();
self.push_backtrace_frame(function, &args);
let result = self.funcall_general_untraced(function, args);
let result = self.dispatch_signal_result_if_needed(result);
self.unbind_to_with_result(bt_count, result)
}
pub(crate) fn funcall_general_untraced(
&mut self,
function: Value,
args: impl Into<LispArgVec>,
) -> EvalResult {
let args = args.into();
match function.kind() {
ValueKind::Veclike(VecLikeType::ByteCode) => {
let bc_data = function.get_bytecode_data().unwrap();
let mut vm = super::bytecode::Vm::from_context(self);
vm.execute_with_func_value(bc_data, args, function)
}
ValueKind::Veclike(VecLikeType::Lambda) => self.apply_lambda(function, args),
ValueKind::Veclike(VecLikeType::Macro) => self.apply_lambda(function, args),
ValueKind::Subr(_) => self.apply_subr_object(function, args, true),
ValueKind::Veclike(VecLikeType::Subr) => self.apply_subr_object(function, args, true),
ValueKind::Symbol(id) => self.apply_symbol_callable_untraced(id, args, true),
ValueKind::T => self.apply_symbol_callable_untraced(intern("t"), args, true),
ValueKind::Nil => Err(signal("void-function", vec![Value::symbol("nil")])),
_ if function.is_symbol_with_pos() => {
let bare = function.as_symbol_with_pos_sym().unwrap();
self.funcall_general_untraced(bare, args)
}
ValueKind::Cons => {
if super::autoload::is_autoload_value(&function) {
Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), function],
))
} else if matches!(
cons_head_symbol_id(&function),
Some(id) if is_lambda_like_symbol_id(id)
) {
match self.instantiate_callable_cons_form(function) {
Ok(callable) => self.apply(callable, args),
Err(_) => Err(signal("invalid-function", vec![function])),
}
} else {
Err(signal("invalid-function", vec![function]))
}
}
_ => Err(signal("invalid-function", vec![function])),
}
}
pub(crate) fn instantiate_callable_cons_form(&mut self, function: Value) -> EvalResult {
if !function.is_cons() {
return Err(signal("invalid-function", vec![function]));
}
if list_length(&function).is_none() {
return Err(signal("invalid-function", vec![function]));
}
let head_val = self.unwrap_symbol(function.cons_car());
let Some(head_id) = head_val.as_symbol_id() else {
return Err(signal("invalid-function", vec![function]));
};
let mut tail = function.cons_cdr();
let (env_value, params_value, is_lambda) = if head_id == lambda_symbol() {
if !tail.is_cons() {
return Err(signal("invalid-function", vec![function]));
}
let params_value = tail.cons_car();
tail = tail.cons_cdr();
let env_value = if !self.lexenv.is_nil() {
self.lexenv
} else {
Value::NIL
};
(env_value, params_value, true)
} else if head_id == closure_symbol() {
if !tail.is_cons() {
return Err(signal("invalid-function", vec![function]));
}
let env_value = tail.cons_car();
tail = tail.cons_cdr();
if !tail.is_cons() {
return Err(signal("invalid-function", vec![function]));
}
let params_value = tail.cons_car();
tail = tail.cons_cdr();
(env_value, params_value, false)
} else {
return Err(signal("invalid-function", vec![function]));
};
let specpdl_root_scope = self.save_specpdl_roots();
self.push_specpdl_root(function);
let docstring_value = if tail.is_cons() {
let value = tail.cons_car();
let rest = tail.cons_cdr();
if value.is_string() && !rest.is_nil() {
tail = rest;
value
} else {
Value::NIL
}
} else {
Value::NIL
};
let mut doc_form_value = Value::NIL;
if tail.is_cons() {
let item = tail.cons_car();
if let Some(doc_form) = self.eval_dynamic_documentation_value(item)? {
doc_form_value = doc_form;
tail = tail.cons_cdr();
}
}
while tail.is_cons() {
let item = tail.cons_car();
if !item.is_cons()
|| item.cons_car().as_symbol_id() != Some(declare_symbol())
|| list_length(&item).is_none()
{
break;
}
tail = tail.cons_cdr();
}
let iform_value = if tail.is_cons() {
let item = tail.cons_car();
if item.is_cons() && item.cons_car().as_symbol_id() == Some(interactive_symbol_id()) {
tail = tail.cons_cdr();
item
} else {
Value::NIL
}
} else {
Value::NIL
};
let body_value = if tail.is_nil() {
Value::list(vec![Value::NIL])
} else {
tail
};
let closure_doc_value = if !doc_form_value.is_nil() {
doc_form_value
} else {
docstring_value
};
self.push_specpdl_root(params_value);
self.push_specpdl_root(body_value);
self.push_specpdl_root(env_value);
self.push_specpdl_root(closure_doc_value);
self.push_specpdl_root(iform_value);
let result = if is_lambda {
self.make_interpreted_closure_with_value_runtime_hook(
function,
params_value,
body_value,
env_value,
closure_doc_value,
iform_value,
)
} else {
builtins::symbols::make_interpreted_closure_from_parts(
¶ms_value,
&body_value,
&env_value,
Some(&closure_doc_value),
Some(&iform_value),
)
};
self.restore_specpdl_roots(specpdl_root_scope);
result
}
#[inline]
fn check_funcall_subr_arity_value(&self, function: Value, nargs: usize) -> Option<Flow> {
let (_, entry) = subr_entry_from_value(function)?;
let min = entry.min_args as usize;
let max = entry.max_args.map(|m| m as usize);
if min == 0 && max.is_none() {
return None;
}
let arity_bad = nargs < min || max.is_some_and(|m| nargs > m);
if arity_bad {
Some(signal(
"wrong-number-of-arguments",
vec![function, Value::fixnum(nargs as i64)],
))
} else {
None
}
}
#[inline]
fn check_funcall_subr_arity(&self, sym_id: SymId, nargs: usize) -> Option<Flow> {
self.check_funcall_subr_arity_value(Value::subr_from_sym_id(sym_id), nargs)
}
fn dispatch_subr_value_internal(
&mut self,
function: Value,
args: LispArgVec,
wrong_arity_callee: Value,
) -> Option<EvalResult> {
let (_, entry) = subr_entry_from_value(function)?;
self.dispatch_subr_entry_internal(entry, args, wrong_arity_callee)
}
fn dispatch_subr_entry_internal(
&mut self,
entry: SubrEntry,
args: LispArgVec,
wrong_arity_callee: Value,
) -> Option<EvalResult> {
let func = entry.function?;
let nargs = args.len();
if (nargs as u16) < entry.min_args {
return Some(Err(signal(
"wrong-number-of-arguments",
vec![wrong_arity_callee, Value::fixnum(nargs as i64)],
)));
}
if let Some(max) = entry.max_args {
if nargs as u16 > max {
return Some(Err(signal(
"wrong-number-of-arguments",
vec![wrong_arity_callee, Value::fixnum(nargs as i64)],
)));
}
}
Some(self.dispatch_subr_func_unchecked(func, args))
}
#[inline]
fn dispatch_subr_entry_unchecked(
&mut self,
entry: SubrEntry,
args: LispArgVec,
) -> Option<EvalResult> {
let func = entry.function?;
Some(self.dispatch_subr_func_unchecked(func, args))
}
#[inline]
fn subr_entry_uses_fixed_value_call(entry: SubrEntry) -> bool {
entry.dispatch_kind == SubrDispatchKind::Builtin
&& matches!(
entry.function,
Some(SubrFn::A0(_) | SubrFn::A1(_) | SubrFn::A2(_) | SubrFn::A3(_))
)
}
#[inline]
fn backtrace_evaluated_arg_or_nil(&self, count: usize, index: usize) -> Value {
match self.specpdl.get(count) {
Some(SpecBinding::Backtrace {
args: BacktraceArgs::Evaluated(args),
..
}) => args.get(index).copied().unwrap_or(Value::NIL),
Some(other) => panic!(
"backtrace_evaluated_arg_or_nil: expected EVALD Backtrace at specpdl[{count}], got {other:?}"
),
None => panic!("backtrace_evaluated_arg_or_nil: specpdl index out of range"),
}
}
#[inline]
fn dispatch_subr_entry_from_backtrace_unchecked(
&mut self,
entry: SubrEntry,
backtrace_count: usize,
) -> Option<EvalResult> {
match entry.function? {
SubrFn::A0(func) => Some(func(self)),
SubrFn::A1(func) => {
let arg0 = self.backtrace_evaluated_arg_or_nil(backtrace_count, 0);
Some(func(self, arg0))
}
SubrFn::A2(func) => {
let arg0 = self.backtrace_evaluated_arg_or_nil(backtrace_count, 0);
let arg1 = self.backtrace_evaluated_arg_or_nil(backtrace_count, 1);
Some(func(self, arg0, arg1))
}
SubrFn::A3(func) => {
let arg0 = self.backtrace_evaluated_arg_or_nil(backtrace_count, 0);
let arg1 = self.backtrace_evaluated_arg_or_nil(backtrace_count, 1);
let arg2 = self.backtrace_evaluated_arg_or_nil(backtrace_count, 2);
Some(func(self, arg0, arg1, arg2))
}
SubrFn::Many(_) | SubrFn::ManySlice(_) => None,
}
}
#[inline]
fn dispatch_subr_func_unchecked(
&mut self,
func: crate::tagged::header::SubrFn,
args: LispArgVec,
) -> EvalResult {
match func {
crate::tagged::header::SubrFn::Many(func) => func(self, args.into_vec()),
crate::tagged::header::SubrFn::ManySlice(func) => func(self, &args),
crate::tagged::header::SubrFn::A0(func) => func(self),
crate::tagged::header::SubrFn::A1(func) => {
func(self, args.first().copied().unwrap_or(Value::NIL))
}
crate::tagged::header::SubrFn::A2(func) => func(
self,
args.first().copied().unwrap_or(Value::NIL),
args.get(1).copied().unwrap_or(Value::NIL),
),
crate::tagged::header::SubrFn::A3(func) => func(
self,
args.first().copied().unwrap_or(Value::NIL),
args.get(1).copied().unwrap_or(Value::NIL),
args.get(2).copied().unwrap_or(Value::NIL),
),
}
}
#[inline]
fn apply_subr_object(
&mut self,
function: Value,
args: LispArgVec,
_rewrite_builtin_wrong_arity: bool,
) -> EvalResult {
let Some((sym_id, entry)) = subr_entry_from_value(function) else {
return Err(signal("invalid-function", vec![function]));
};
self.apply_subr_object_with_entry(sym_id, function, args, entry)
}
#[inline]
fn apply_subr_object_with_entry(
&mut self,
sym_id: SymId,
function: Value,
args: LispArgVec,
entry: SubrEntry,
) -> EvalResult {
if entry.dispatch_kind == SubrDispatchKind::SpecialForm {
return Err(signal("invalid-function", vec![function]));
}
if entry.dispatch_kind == SubrDispatchKind::ContextCallable {
return self.apply_evaluator_callable_by_id(sym_id, args);
}
if let Some(result) = self.dispatch_subr_entry_internal(entry, args, function) {
result.map_err(|flow| self.validate_throw(flow))
} else {
Err(signal("void-function", vec![Value::from_sym_id(sym_id)]))
}
}
#[inline]
fn resolve_named_call_target_by_id(&mut self, sym_id: SymId) -> NamedCallTarget {
let compiler_overrides_active =
compiler_function_overrides_active_in_obarray(&self.obarray);
let function_epoch = self.obarray.function_epoch();
if !compiler_overrides_active {
if let Some(entry) = self.named_call_cache.get(&sym_id)
&& entry.function_epoch == function_epoch
{
return entry.target.clone();
}
}
let target =
if let Some(func) = compiler_function_override_in_obarray(&self.obarray, sym_id) {
NamedCallTarget::Obarray(func)
} else if let Some(func) = self.obarray.symbol_function_id(sym_id) {
match func.kind() {
ValueKind::Nil => NamedCallTarget::Void,
ValueKind::Subr(sid) if sid == sym_id => {
NamedCallTarget::Subr(Value::subr_from_sym_id(sid))
}
ValueKind::Veclike(VecLikeType::Subr) if func.as_subr_id() == Some(sym_id) => {
NamedCallTarget::Subr(func)
}
_ => NamedCallTarget::Obarray(func),
}
} else if self.obarray.is_function_unbound_id(sym_id) {
NamedCallTarget::Void
} else if lookup_global_subr_entry(sym_id).is_some() {
NamedCallTarget::Subr(Value::subr_from_sym_id(sym_id))
} else {
NamedCallTarget::Void
};
if !compiler_overrides_active {
if self.named_call_cache.len() < NAMED_CALL_CACHE_CAPACITY {
self.named_call_cache.insert(
sym_id,
NamedCallCacheEntry {
function_epoch,
target: target.clone(),
},
);
}
}
target
}
#[inline]
fn resolve_named_call_target(&mut self, name: &str) -> NamedCallTarget {
self.resolve_named_call_target_by_id(intern(name))
}
#[inline]
fn store_named_call_cache(&mut self, symbol: SymId, target: NamedCallTarget) {
let function_epoch = self.obarray.function_epoch();
if self.named_call_cache.len() < NAMED_CALL_CACHE_CAPACITY {
self.named_call_cache.insert(
symbol,
NamedCallCacheEntry {
function_epoch,
target,
},
);
}
}
#[inline]
fn apply_named_callable_by_id(
&mut self,
sym_id: SymId,
args: LispArgVec,
invalid_fn: Value,
rewrite_builtin_wrong_arity: bool,
) -> EvalResult {
let frame_function = Value::from_sym_id(sym_id);
let bt_count = self.specpdl.len();
self.push_backtrace_frame(frame_function, &args);
let result = self.apply_named_callable_by_id_core(
sym_id,
args,
invalid_fn,
rewrite_builtin_wrong_arity,
);
self.unbind_to_with_result(bt_count, result)
}
#[inline]
fn apply_named_callable(
&mut self,
name: &str,
args: LispArgVec,
invalid_fn: Value,
rewrite_builtin_wrong_arity: bool,
) -> EvalResult {
let frame_function = Value::symbol(name);
let bt_count = self.specpdl.len();
self.push_backtrace_frame(frame_function, &args);
let result =
self.apply_named_callable_core(name, args, invalid_fn, rewrite_builtin_wrong_arity);
self.unbind_to_with_result(bt_count, result)
}
fn apply_named_callable_by_id_core(
&mut self,
sym_id: SymId,
args: LispArgVec,
invalid_fn: Value,
rewrite_builtin_wrong_arity: bool,
) -> EvalResult {
match self.resolve_named_call_target_by_id(sym_id) {
NamedCallTarget::Obarray(func) => {
if super::autoload::is_autoload_value(&func) {
return self.apply_named_autoload_callable_by_id(
sym_id,
func,
args,
rewrite_builtin_wrong_arity,
);
}
let function_is_callable = self.function_value_is_callable(&func);
let result = match self.apply_untraced(func, args) {
Err(Flow::Signal(sig))
if sig.symbol_name() == "invalid-function" && !function_is_callable =>
{
Err(signal("invalid-function", vec![Value::from_sym_id(sym_id)]))
}
other => other,
};
result
}
NamedCallTarget::Subr(func) => {
let Some((sym_id, entry)) = subr_entry_from_value(func) else {
return Err(signal("invalid-function", vec![invalid_fn]));
};
if entry.dispatch_kind == SubrDispatchKind::SpecialForm {
return Err(signal("invalid-function", vec![invalid_fn]));
}
self.apply_subr_object_with_entry(sym_id, func, args, entry)
}
NamedCallTarget::Void => Err(signal("void-function", vec![Value::from_sym_id(sym_id)])),
}
}
fn apply_named_callable_core(
&mut self,
name: &str,
args: LispArgVec,
invalid_fn: Value,
rewrite_builtin_wrong_arity: bool,
) -> EvalResult {
match self.resolve_named_call_target(name) {
NamedCallTarget::Obarray(func) => {
if super::autoload::is_autoload_value(&func) {
return self.apply_named_autoload_callable(
name,
func,
args,
rewrite_builtin_wrong_arity,
);
}
let function_is_callable = self.function_value_is_callable(&func);
let result = match self.apply(func, args) {
Err(Flow::Signal(sig))
if sig.symbol_name() == "invalid-function" && !function_is_callable =>
{
Err(signal("invalid-function", vec![Value::symbol(name)]))
}
other => other,
};
result
}
NamedCallTarget::Subr(func) => {
let sym_id = intern(name);
let result = self.apply_subr_object(func, args, rewrite_builtin_wrong_arity);
if func
.as_subr_id()
.and_then(lookup_global_subr_entry)
.is_some_and(|e| e.dispatch_kind == SubrDispatchKind::SpecialForm)
{
Err(signal("invalid-function", vec![invalid_fn]))
} else {
result
}
}
NamedCallTarget::Void => Err(signal("void-function", vec![Value::symbol(name)])),
}
}
fn apply_named_autoload_callable(
&mut self,
name: &str,
autoload_form: Value,
args: LispArgVec,
rewrite_builtin_wrong_arity: bool,
) -> EvalResult {
self.apply_named_autoload_callable_by_id(
intern(name),
autoload_form,
args,
rewrite_builtin_wrong_arity,
)
}
fn apply_named_autoload_callable_by_id(
&mut self,
sym_id: SymId,
autoload_form: Value,
args: LispArgVec,
rewrite_builtin_wrong_arity: bool,
) -> EvalResult {
if lookup_global_subr_entry(sym_id).is_some() {
let subr = Value::subr_from_sym_id(sym_id);
if let Some(flow) = self.check_funcall_subr_arity_value(subr, args.len()) {
return Err(flow);
}
if let Some(result) = self.dispatch_subr_value_internal(
subr,
args.clone(),
Value::subr_from_sym_id(sym_id),
) {
return result;
}
}
let loaded = super::autoload::builtin_autoload_do_load(
self,
vec![autoload_form, Value::from_sym_id(sym_id)],
)?;
let function_is_callable = self.function_value_is_callable(&loaded);
match self.apply_untraced(loaded, args) {
Err(Flow::Signal(sig))
if sig.symbol_name() == "invalid-function" && !function_is_callable =>
{
Err(signal("invalid-function", vec![Value::from_sym_id(sym_id)]))
}
other => other,
}
}
fn apply_evaluator_callable(
&mut self,
name: &str,
args: LispArgVec,
wrong_arity_callee: Value,
) -> EvalResult {
match name {
"throw" => {
if args.len() != 2 {
return Err(signal(
"wrong-number-of-arguments",
vec![wrong_arity_callee, Value::fixnum(args.len() as i64)],
));
}
let tag = args[0];
let value = args[1];
if self.has_active_catch(&tag) {
Err(Flow::Throw { tag, value })
} else {
Err(signal("no-catch", vec![tag, value]))
}
}
_ => Err(signal("void-function", vec![Value::symbol(name)])),
}
}
fn apply_evaluator_callable_by_id(&mut self, sym_id: SymId, args: LispArgVec) -> EvalResult {
if sym_id == throw_symbol() {
if args.len() != 2 {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::subr_from_sym_id(sym_id),
Value::fixnum(args.len() as i64),
],
));
}
let tag = args[0];
let value = args[1];
if self.has_active_catch(&tag) {
Err(Flow::Throw { tag, value })
} else {
Err(signal("no-catch", vec![tag, value]))
}
} else {
Err(signal("void-function", vec![Value::from_sym_id(sym_id)]))
}
}
fn apply_lambda(&mut self, func_value: Value, args: LispArgVec) -> EvalResult {
let Some(params) = func_value.closure_params() else {
return Err(signal("invalid-function", vec![func_value]));
};
let Some(body) = func_value.closure_body_value() else {
return Err(signal("invalid-function", vec![func_value]));
};
let env = func_value.closure_env().unwrap_or(None);
let root_count = self.specpdl.len();
self.specpdl.push(SpecBinding::GcRoot { value: func_value });
let call_state = match self.begin_lambda_call(params, env, &args) {
Ok(state) => state,
Err(err) => {
self.unbind_to(root_count);
return Err(err);
}
};
let result = self.eval_lambda_body_value(body);
self.finish_lambda_call(call_state);
self.unbind_to(root_count);
result
}
#[inline]
fn bind_lexical_value_rooted(&mut self, sym: SymId, value: Value) {
bind_lexical_value_rooted_in_specpdl(&mut self.lexenv, &mut self.specpdl, sym, value);
}
pub(crate) fn with_macro_expansion_scope<T>(
&mut self,
f: impl FnOnce(&mut Self) -> Result<T, Flow>,
) -> Result<T, Flow> {
self.macro_expansion_scope_depth += 1;
let scope_enter_start = self.macro_perf_enabled.then(std::time::Instant::now);
let state = begin_macro_expansion_scope_in_state(
&mut self.obarray,
&mut self.specpdl,
&mut self.buffers,
&self.custom,
self.lexenv,
);
if let Some(start) = scope_enter_start {
self.macro_perf_stats
.scope_enter
.note_duration(start.elapsed());
}
let result = f(self);
let scope_exit_start = self.macro_perf_enabled.then(std::time::Instant::now);
finish_macro_expansion_scope_in_state(
&mut self.obarray,
&mut self.specpdl,
&mut self.buffers,
&self.custom,
state,
);
if let Some(start) = scope_exit_start {
self.macro_perf_stats
.scope_exit
.note_duration(start.elapsed());
}
self.macro_expansion_scope_depth = self.macro_expansion_scope_depth.saturating_sub(1);
result
}
#[inline]
pub(crate) fn macro_expansion_mutation_epoch(&self) -> u64 {
self.macro_expansion_mutation_epoch
}
#[inline]
pub(crate) fn note_macro_expansion_mutation(&mut self) {
if self.macro_expansion_scope_depth > 0 {
self.macro_expansion_mutation_epoch =
self.macro_expansion_mutation_epoch.wrapping_add(1);
}
}
fn macro_expansion_context_key(&self) -> u64 {
self.macro_expansion_context_key_for_environment(None)
}
fn macro_expansion_context_key_for_environment(&self, environment: Option<Value>) -> u64 {
fn value_identity_key(value: Value) -> u64 {
match value.kind() {
ValueKind::Nil => 0,
ValueKind::T => 1,
ValueKind::Fixnum(n) => ((n as u64).wrapping_mul(0x9E37_79B1)) ^ 0x10,
ValueKind::Symbol(sym) => ((sym.0 as u64) << 8) ^ 0x20,
ValueKind::Subr(sym) => ((sym.0 as u64) << 8) ^ 0x22,
ValueKind::Veclike(VecLikeType::Subr) => {
let sym = value.as_subr_id().unwrap();
((sym.0 as u64) << 8) ^ 0x22
}
_ => (value.bits() as u64) ^ 0x30,
}
}
fn semantic_fingerprint(value: Value) -> u64 {
use std::hash::Hasher;
let mut hasher = std::collections::hash_map::DefaultHasher::new();
let mut seen = std::collections::HashSet::new();
value_fingerprint(value, &mut hasher, 4, &mut seen);
hasher.finish()
}
let current_macroexpand_env = self
.obarray()
.symbol_value_id_or_nil(macroexpand_all_environment_symbol());
let current_dynvars = self
.obarray()
.symbol_value_id_or_nil(macroexp_dynvars_symbol());
let explicit_environment_key = environment.map(semantic_fingerprint).unwrap_or(0);
let current_macroexpand_env_key = value_identity_key(current_macroexpand_env);
let current_dynvars_key = value_identity_key(current_dynvars);
explicit_environment_key.rotate_left(7)
^ current_macroexpand_env_key.rotate_left(29)
^ current_dynvars_key.rotate_left(43)
}
fn runtime_macro_expansion_cache_enabled(&self) -> bool {
!self.macro_cache_disabled
&& self
.visible_variable_value_or_nil_by_id(load_in_progress_symbol())
.is_truthy()
}
fn runtime_macro_expansion_cache_key(
&self,
function: Value,
args_fingerprint: u64,
context_key: u64,
) -> (usize, usize, u64) {
(
function.bits() ^ 0x9E37_79B1usize,
args_fingerprint as usize,
context_key,
)
}
pub(crate) fn lookup_runtime_macro_expansion(
&mut self,
function: Value,
args: &[Value],
environment: Option<Value>,
) -> Option<Value> {
let perf_start = self.macro_perf_enabled.then(std::time::Instant::now);
if !self.runtime_macro_expansion_cache_enabled() {
if let Some(start) = perf_start {
self.macro_perf_stats
.cache_lookup
.note_duration(start.elapsed());
}
return None;
}
let current_fp = runtime_tail_fingerprint(args);
let context_key = self.macro_expansion_context_key_for_environment(environment);
let cache_key = self.runtime_macro_expansion_cache_key(function, current_fp, context_key);
let cached = self
.runtime_macro_expansion_cache
.get(&cache_key)
.cloned()?;
if cached.fingerprint == current_fp {
self.macro_cache_hits += 1;
if let Some(start) = perf_start {
self.macro_perf_stats
.cache_lookup
.note_duration(start.elapsed());
}
return Some(cached.expanded);
}
if let Some(start) = perf_start {
self.macro_perf_stats
.cache_lookup
.note_duration(start.elapsed());
}
None
}
pub(crate) fn store_runtime_macro_expansion(
&mut self,
form: Value,
function: Value,
args: &[Value],
expanded_value: &Value,
expand_elapsed: std::time::Duration,
environment: Option<Value>,
) {
let perf_start = self.macro_perf_enabled.then(std::time::Instant::now);
if !self.runtime_macro_expansion_cache_enabled() {
if let Some(start) = perf_start {
self.macro_perf_stats
.cache_store
.note_duration(start.elapsed());
}
return;
}
self.macro_cache_misses += 1;
self.macro_expand_total_us += expand_elapsed.as_micros() as u64;
let current_fp = runtime_tail_fingerprint(args);
let context_key = self.macro_expansion_context_key_for_environment(environment);
let cache_key = self.runtime_macro_expansion_cache_key(function, current_fp, context_key);
let cache_entry =
RuntimeMacroExpansionCacheEntry::new(function, *expanded_value, current_fp);
if self.macro_perf_enabled && expand_elapsed.as_millis() > 50 {
let macro_head = if form.is_cons() {
form.cons_car().as_symbol_name().unwrap_or("<non-symbol>")
} else {
"<atom>"
};
let form_str = crate::emacs_core::print::print_value(&form);
let form_preview: String = form_str.chars().take(200).collect();
tracing::warn!(
"runtime_macro_cache MISS head={macro_head} macro={:#x} fp={:#x} took {expand_elapsed:.2?} form={form_preview}",
function.bits(),
current_fp
);
}
self.runtime_macro_expansion_cache
.insert(cache_key, cache_entry);
if let Some(start) = perf_start {
self.macro_perf_stats
.cache_store
.note_duration(start.elapsed());
}
}
fn apply_macro_callable_with_dynamic_scope(
&mut self,
callable: Value,
args: Vec<Value>,
) -> Result<Value, Flow> {
let perf_start = self.macro_perf_enabled.then(std::time::Instant::now);
let result = self.with_macro_expansion_scope(|eval| eval.apply(callable, args));
if let Some(start) = perf_start {
self.macro_perf_stats
.macro_apply
.note_duration(start.elapsed());
}
result
}
pub(crate) fn expand_macro_for_macroexpand(
&mut self,
form: Value,
definition: Value,
args: Vec<Value>,
environment: Option<Value>,
) -> Result<Value, Flow> {
let perf_start = self.macro_perf_enabled.then(std::time::Instant::now);
if let Some(cached) = self.lookup_runtime_macro_expansion(definition, &args, environment) {
if let Some(start) = perf_start {
self.macro_perf_stats
.expand_macro
.note_duration(start.elapsed());
}
return Ok(cached);
}
let args_for_cache = args.clone();
let expand_start = std::time::Instant::now();
let specpdl_root_scope = self.save_specpdl_roots();
self.push_specpdl_root(form);
self.push_specpdl_root(definition);
if let Some(environment) = environment {
self.push_specpdl_root(environment);
}
let result = (|| {
let expanded = if definition.is_macro() {
self.apply_macro_callable_with_dynamic_scope(definition, args)?
} else if cons_head_symbol_id(&definition) == Some(macro_symbol()) {
self.apply_macro_callable_with_dynamic_scope(definition.cons_cdr(), args)?
} else if self.function_value_is_callable(&definition) {
self.apply_macro_callable_with_dynamic_scope(definition, args)?
} else {
return Err(signal("invalid-function", vec![definition]));
};
let expand_elapsed = expand_start.elapsed();
self.store_runtime_macro_expansion(
form,
definition,
&args_for_cache,
&expanded,
expand_elapsed,
environment,
);
Ok(expanded)
})();
self.restore_specpdl_roots(specpdl_root_scope);
if let Some(start) = perf_start {
self.macro_perf_stats
.expand_macro
.note_duration(start.elapsed());
}
result
}
pub(crate) fn note_eager_macro_perf_step1(&mut self, duration: std::time::Duration) {
if self.macro_perf_enabled {
self.macro_perf_stats.eager_step1.note_duration(duration);
}
}
pub(crate) fn note_eager_macro_perf_step3(&mut self, duration: std::time::Duration) {
if self.macro_perf_enabled {
self.macro_perf_stats.eager_step3.note_duration(duration);
}
}
pub(crate) fn note_eager_macro_perf_step4(&mut self, duration: std::time::Duration) {
if self.macro_perf_enabled {
self.macro_perf_stats.eager_step4.note_duration(duration);
}
}
pub(crate) fn macro_perf_summary(&self) -> Option<String> {
if !self.macro_perf_enabled {
return None;
}
let mut parts = vec![format!(
"cache=hits:{} misses:{} expand-total:{:.2}ms",
self.macro_cache_hits,
self.macro_cache_misses,
self.macro_expand_total_us as f64 / 1000.0
)];
for counter in [
self.macro_perf_stats.scope_enter.summary("scope-enter"),
self.macro_perf_stats.scope_exit.summary("scope-exit"),
self.macro_perf_stats.macro_apply.summary("macro-apply"),
self.macro_perf_stats.cache_lookup.summary("cache-lookup"),
self.macro_perf_stats.cache_store.summary("cache-store"),
self.macro_perf_stats.expand_macro.summary("expand-macro"),
self.macro_perf_stats.eager_step1.summary("eager-step1"),
self.macro_perf_stats.eager_step3.summary("eager-step3"),
self.macro_perf_stats.eager_step4.summary("eager-step4"),
]
.into_iter()
.flatten()
{
parts.push(counter);
}
Some(parts.join(" | "))
}
#[inline]
pub(crate) fn macro_perf_enabled(&self) -> bool {
self.macro_perf_enabled
}
pub(crate) fn specbind(&mut self, sym_id: SymId, value: Value) {
let resolved =
builtins::resolve_variable_alias_id_in_obarray(&self.obarray, sym_id).unwrap_or(sym_id);
let name = resolve_sym(resolved);
if name == "macroexpand-all-environment" && !value.is_nil() && !value.is_cons() {
tracing::error!(
"specbind macroexpand-all-environment to non-list: {:?} bits={:#x}",
value.kind(),
value.bits()
);
}
{
use crate::emacs_core::forward::{LispBufferObjFwd, LispFwdType};
use crate::emacs_core::symbol::SymbolRedirect;
let forwarded = self
.obarray
.get_by_id(resolved)
.filter(|s| s.redirect() == SymbolRedirect::Forwarded)
.map(|s| unsafe { s.val.fwd });
if let Some(fwd_ptr) = forwarded {
let fwd = unsafe { &*fwd_ptr };
if matches!(fwd.ty, LispFwdType::BufferObj) {
let buf_fwd = unsafe { &*(fwd as *const _ as *const LispBufferObjFwd) };
let off = buf_fwd.offset as usize;
let flags_idx = buf_fwd.local_flags_idx;
let buf_id_opt = self.buffers.current_buffer_id();
let has_local = match buf_id_opt {
Some(id) => self
.buffers
.get(id)
.map(|buf| flags_idx < 0 || buf.slot_local_flag(off))
.unwrap_or(false),
None => false,
};
if has_local {
let buf_id = buf_id_opt.expect("has_local implies current buffer");
let old_val = self
.buffers
.get(buf_id)
.map(|b| b.slots[off])
.unwrap_or(Value::NIL);
self.specpdl.push(SpecBinding::LetLocal {
sym_id: resolved,
old_value: old_val,
buffer_id: buf_id,
});
if self.watchers.has_watchers(resolved) {
let _ = self.run_variable_watchers_by_id(
resolved,
&value,
&Value::NIL,
"let",
);
}
if let Some(buf) = self.buffers.get_mut(buf_id) {
buf.slots[off] = value;
}
return;
} else {
let old_default = if off < self.buffers.buffer_defaults.len() {
Some(self.buffers.buffer_defaults[off])
} else {
Some(buf_fwd.default)
};
self.specpdl.push(SpecBinding::LetDefault {
sym_id: resolved,
old_value: old_default,
});
if self.watchers.has_watchers(resolved) {
let _ = self.run_variable_watchers_by_id(
resolved,
&value,
&Value::NIL,
"let",
);
}
let info_ref =
crate::buffer::buffer::lookup_buffer_slot_by_sym_id(resolved);
if let Some(info) = info_ref {
self.buffers.set_buffer_default_slot(info, value);
}
return;
}
}
}
}
if let Some(sym_slot) = self.obarray.get_by_id(resolved)
&& sym_slot.redirect() == crate::emacs_core::symbol::SymbolRedirect::Localized
{
if let Some(buf_id) = self.buffers.current_buffer_id() {
let (cur_val, alist) = match self.buffers.get(buf_id) {
Some(buf) => (Value::make_buffer(buf.id), buf.local_var_alist),
None => (Value::NIL, Value::NIL),
};
let old_val = self
.obarray
.find_symbol_value_in_buffer(
resolved,
Some(buf_id),
cur_val,
alist,
None,
0u64,
None,
)
.unwrap_or(Value::NIL);
let blv_found = self.obarray.blv(resolved).map(|b| b.found).unwrap_or(false);
if blv_found {
self.specpdl.push(SpecBinding::LetLocal {
sym_id: resolved,
old_value: old_val,
buffer_id: buf_id,
});
} else {
self.specpdl.push(SpecBinding::LetDefault {
sym_id: resolved,
old_value: Some(old_val),
});
}
if self.watchers.has_watchers(resolved) {
let _ = self.run_variable_watchers_by_id(resolved, &value, &Value::NIL, "let");
}
let new_alist = self.obarray.set_internal_localized(
resolved,
value,
cur_val,
alist,
crate::emacs_core::symbol::SetInternalBind::Bind,
false,
);
if let Some(buf) = self.buffers.get_mut(buf_id) {
buf.local_var_alist = new_alist;
}
self.sync_cached_runtime_binding_by_id(resolved, value);
return;
}
}
let old_value = self.obarray.symbol_value_id(resolved).copied();
self.specpdl.push(SpecBinding::Let {
sym_id: resolved,
old_value,
});
if self.watchers.has_watchers(resolved) {
let _ = self.run_variable_watchers_by_id(resolved, &value, &Value::NIL, "let");
}
self.obarray.set_symbol_value_id(resolved, value);
self.sync_cached_runtime_binding_by_id(resolved, value);
}
pub(crate) fn let_shadows_buffer_binding_p(&self, sym_id: SymId) -> bool {
self.specpdl.iter().rev().any(|entry| match entry {
SpecBinding::LetDefault { sym_id: s, .. } => *s == sym_id,
SpecBinding::LetLocal { sym_id: s, .. } => *s == sym_id,
SpecBinding::Let { .. }
| SpecBinding::LexicalEnv { .. }
| SpecBinding::GcRoot { .. }
| SpecBinding::Backtrace { .. }
| SpecBinding::Nop
| SpecBinding::UnwindProtect { .. }
| SpecBinding::SaveExcursion { .. }
| SpecBinding::SaveCurrentBuffer { .. }
| SpecBinding::SaveRestriction { .. } => false,
})
}
fn restore_default_binding_by_id(&mut self, sym_id: SymId, old_value: Option<Value>) {
use crate::emacs_core::forward::{LispBufferObjFwd, LispFwdType};
use crate::emacs_core::symbol::SymbolRedirect;
let forwarded_slot = self
.obarray
.get_by_id(sym_id)
.filter(|s| s.redirect() == SymbolRedirect::Forwarded)
.and_then(|s| {
let fwd = unsafe { &*s.val.fwd };
if matches!(fwd.ty, LispFwdType::BufferObj) {
let buf_fwd = unsafe { &*(fwd as *const _ as *const LispBufferObjFwd) };
crate::buffer::buffer::lookup_buffer_slot_by_sym_id(sym_id)
.map(|info| (info, buf_fwd))
} else {
None
}
});
if let Some((info, _buf_fwd)) = forwarded_slot {
if let Some(val) = old_value {
self.buffers.set_buffer_default_slot(info, val);
}
return;
}
match old_value {
Some(val) => {
self.obarray.set_symbol_value_id(sym_id, val);
self.sync_cached_runtime_binding_by_id(sym_id, val);
}
None => {
if self
.obarray
.get_by_id(sym_id)
.is_some_and(|s| s.redirect() == SymbolRedirect::Localized)
{
self.obarray.set_symbol_value_id(sym_id, Value::UNBOUND);
} else {
self.obarray.makunbound_id(sym_id);
}
self.sync_cached_runtime_binding_by_id(sym_id, Value::NIL);
}
}
}
pub(crate) fn unbind_to(&mut self, count: usize) {
let quitf = self.quit_flag_value();
if !quitf.is_nil() {
self.set_quit_flag_value(Value::NIL);
}
while self.specpdl.len() > count {
let binding = self.specpdl.pop().unwrap();
match binding {
SpecBinding::Let { sym_id, old_value } => {
if self.watchers.has_watchers(sym_id) {
let restore_val = old_value.unwrap_or(Value::NIL);
let _ = self.run_variable_watchers_by_id(
sym_id,
&restore_val,
&Value::NIL,
"unlet",
);
}
let still_plain = self.obarray.get_by_id(sym_id).is_none_or(|s| {
s.redirect() == crate::emacs_core::symbol::SymbolRedirect::Plainval
});
if still_plain {
match old_value {
Some(val) => {
self.obarray.set_symbol_value_id(sym_id, val);
self.sync_cached_runtime_binding_by_id(sym_id, val);
}
None => {
self.obarray.makunbound_id(sym_id);
self.sync_cached_runtime_binding_by_id(sym_id, Value::NIL);
}
}
} else {
self.restore_default_binding_by_id(sym_id, old_value);
}
}
SpecBinding::LetLocal {
sym_id,
old_value,
buffer_id,
} => {
if self.watchers.has_watchers(sym_id) {
let _ = self.run_variable_watchers_by_id(
sym_id,
&old_value,
&Value::NIL,
"unlet",
);
}
if self.buffers.get(buffer_id).is_some() {
use crate::emacs_core::symbol::{SetInternalBind, SymbolRedirect};
let is_localized = self
.obarray
.get_by_id(sym_id)
.map(|s| s.redirect() == SymbolRedirect::Localized)
.unwrap_or(false);
if is_localized {
let buf_val = Value::make_buffer(buffer_id);
let alist = self
.buffers
.get(buffer_id)
.map(|b| b.local_var_alist)
.unwrap_or(Value::NIL);
let new_alist = self.obarray.set_internal_localized(
sym_id,
old_value,
buf_val,
alist,
SetInternalBind::Unbind,
false,
);
if let Some(buf) = self.buffers.get_mut(buffer_id) {
buf.local_var_alist = new_alist;
}
} else {
let _ = self
.buffers
.set_buffer_local_property_by_sym_id(buffer_id, sym_id, old_value);
}
self.sync_cached_runtime_binding_by_id(sym_id, old_value);
}
}
SpecBinding::LetDefault { sym_id, old_value } => {
if self.watchers.has_watchers(sym_id) {
let restore_val = old_value.unwrap_or(Value::NIL);
let _ = self.run_variable_watchers_by_id(
sym_id,
&restore_val,
&Value::NIL,
"unlet",
);
}
self.restore_default_binding_by_id(sym_id, old_value);
}
SpecBinding::LexicalEnv { old_lexenv } => {
self.lexenv = old_lexenv;
}
SpecBinding::GcRoot { .. } => {}
SpecBinding::Backtrace { .. } => {
}
SpecBinding::Nop => {
}
SpecBinding::UnwindProtect {
forms: cleanup,
lexenv,
} => {
let saved_lexenv = self.lexenv;
self.lexenv = lexenv;
if cleanup.is_cons() || cleanup.is_nil() {
let _ = self.sf_progn_value(cleanup);
} else {
let _ = self.apply(cleanup, vec![]);
}
self.lexenv = saved_lexenv;
}
SpecBinding::SaveExcursion {
buffer_id,
marker_id,
marker: _,
} => {
self.restore_current_buffer_if_live(buffer_id);
if let Some(saved_pt) = self.buffers.marker_position(buffer_id, marker_id) {
let _ = self.buffers.goto_buffer_byte(buffer_id, saved_pt);
}
self.buffers.remove_marker(marker_id);
}
SpecBinding::SaveCurrentBuffer { buffer_id } => {
self.restore_current_buffer_if_live(buffer_id);
}
SpecBinding::SaveRestriction { state } => {
self.buffers.restore_saved_restriction_state(state);
}
}
}
if !quitf.is_nil() && self.quit_flag_value().is_nil() {
self.set_quit_flag_value(quitf);
}
}
}
pub(crate) fn specbind_in_state(
obarray: &mut Obarray,
specpdl: &mut Vec<SpecBinding>,
sym_id: SymId,
value: Value,
) {
let resolved =
super::builtins::resolve_variable_alias_id_in_obarray(obarray, sym_id).unwrap_or(sym_id);
let old_value = obarray.symbol_value_id(resolved).copied();
specpdl.push(SpecBinding::Let {
sym_id: resolved,
old_value,
});
obarray.set_symbol_value_id(resolved, value);
}
pub(crate) fn unbind_to_in_state(
obarray: &mut Obarray,
specpdl: &mut Vec<SpecBinding>,
count: usize,
) {
while specpdl.len() > count {
let binding = specpdl.pop().unwrap();
match binding {
SpecBinding::Let { sym_id, old_value } => match old_value {
Some(val) => obarray.set_symbol_value_id(sym_id, val),
None => obarray.makunbound_id(sym_id),
},
SpecBinding::LetLocal {
sym_id, old_value, ..
} => {
tracing::warn!(
"unbind_to_in_state: LetLocal for {} without buffer manager",
resolve_sym(sym_id)
);
obarray.set_symbol_value_id(sym_id, old_value);
}
SpecBinding::LetDefault { sym_id, old_value } => match old_value {
Some(val) => obarray.set_symbol_value_id(sym_id, val),
None => obarray.makunbound_id(sym_id),
},
SpecBinding::LexicalEnv { .. } => {
tracing::warn!("unbind_to_in_state: LexicalEnv without Context");
}
SpecBinding::GcRoot { .. } => {}
SpecBinding::Backtrace { .. }
| SpecBinding::Nop
| SpecBinding::UnwindProtect { .. }
| SpecBinding::SaveExcursion { .. }
| SpecBinding::SaveCurrentBuffer { .. }
| SpecBinding::SaveRestriction { .. } => {
}
}
}
}
fn default_toplevel_binding(specpdl: &[SpecBinding], sym_id: SymId) -> Option<&SpecBinding> {
specpdl.iter().find(|binding| match binding {
SpecBinding::Let {
sym_id: binding_sym,
..
}
| SpecBinding::LetDefault {
sym_id: binding_sym,
..
} => *binding_sym == sym_id,
SpecBinding::LetLocal { .. }
| SpecBinding::LexicalEnv { .. }
| SpecBinding::GcRoot { .. }
| SpecBinding::Backtrace { .. }
| SpecBinding::Nop
| SpecBinding::UnwindProtect { .. }
| SpecBinding::SaveExcursion { .. }
| SpecBinding::SaveCurrentBuffer { .. }
| SpecBinding::SaveRestriction { .. } => false,
})
}
pub(crate) fn default_toplevel_value_in_state(
obarray: &Obarray,
specpdl: &[SpecBinding],
buffer_defaults: Option<&[Value]>,
sym_id: SymId,
) -> Option<Value> {
match default_toplevel_binding(specpdl, sym_id) {
Some(SpecBinding::Let { old_value, .. })
| Some(SpecBinding::LetDefault { old_value, .. }) => *old_value,
Some(SpecBinding::LetLocal { .. })
| Some(SpecBinding::LexicalEnv { .. })
| Some(SpecBinding::GcRoot { .. })
| Some(SpecBinding::Backtrace { .. })
| Some(SpecBinding::Nop)
| Some(SpecBinding::UnwindProtect { .. })
| Some(SpecBinding::SaveExcursion { .. })
| Some(SpecBinding::SaveCurrentBuffer { .. })
| Some(SpecBinding::SaveRestriction { .. }) => {
unreachable!("non-variable bindings are excluded above")
}
None => {
use crate::emacs_core::forward::{LispBufferObjFwd, LispFwdType};
use crate::emacs_core::symbol::SymbolRedirect;
if let Some(sym) = obarray.get_by_id(sym_id)
&& sym.redirect() == SymbolRedirect::Forwarded
{
let fwd = unsafe { &*sym.val.fwd };
if matches!(fwd.ty, LispFwdType::BufferObj) {
let buf_fwd = unsafe { &*(fwd as *const _ as *const LispBufferObjFwd) };
let off = buf_fwd.offset as usize;
if let Some(defaults) = buffer_defaults
&& off < defaults.len()
{
return Some(defaults[off]);
}
return Some(buf_fwd.default);
}
}
obarray.default_value_id(sym_id).copied()
}
}
}
pub(crate) fn set_default_toplevel_value_in_state(
specpdl: &mut [SpecBinding],
sym_id: SymId,
value: Value,
) -> bool {
for binding in specpdl.iter_mut() {
match binding {
SpecBinding::Let {
sym_id: binding_sym,
old_value,
}
| SpecBinding::LetDefault {
sym_id: binding_sym,
old_value,
} if *binding_sym == sym_id => {
*old_value = Some(value);
return true;
}
SpecBinding::Let { .. }
| SpecBinding::LetDefault { .. }
| SpecBinding::LetLocal { .. }
| SpecBinding::LexicalEnv { .. }
| SpecBinding::GcRoot { .. }
| SpecBinding::Backtrace { .. }
| SpecBinding::Nop
| SpecBinding::UnwindProtect { .. }
| SpecBinding::SaveExcursion { .. }
| SpecBinding::SaveCurrentBuffer { .. }
| SpecBinding::SaveRestriction { .. } => {}
}
}
false
}
pub(crate) fn set_runtime_binding_in_state(
ctx: &mut Context,
sym_id: SymId,
value: Value,
) -> Option<crate::buffer::BufferId> {
set_runtime_binding(
&mut ctx.obarray,
&mut ctx.buffers,
&ctx.custom,
ctx.specpdl.as_slice(),
sym_id,
value,
)
}
pub(crate) fn set_runtime_binding(
obarray: &mut Obarray,
buffers: &mut BufferManager,
_custom: &CustomManager,
specpdl: &[SpecBinding],
sym_id: SymId,
value: Value,
) -> Option<crate::buffer::BufferId> {
use crate::emacs_core::symbol::{SetInternalBind, SymbolRedirect};
let symbol_is_canonical = super::builtins::is_canonical_symbol_id(sym_id);
let redirect = obarray.get_by_id(sym_id).map(|s| s.redirect());
if symbol_is_canonical
&& matches!(redirect, Some(SymbolRedirect::Localized))
&& let Some(buf_id) = buffers.current_buffer_id()
{
let (cur_val, alist) = match buffers.get(buf_id) {
Some(buf) => (Value::make_buffer(buf.id), buf.local_var_alist),
None => (Value::NIL, Value::NIL),
};
let let_shadows = specpdl.iter().rev().any(
|entry| matches!(entry, SpecBinding::LetDefault { sym_id: s, .. } if *s == sym_id),
);
let new_alist = obarray.set_internal_localized(
sym_id,
value,
cur_val,
alist,
SetInternalBind::Set,
let_shadows,
);
if let Some(buf) = buffers.get_mut(buf_id) {
buf.local_var_alist = new_alist;
}
return Some(buf_id);
}
if symbol_is_canonical
&& matches!(redirect, Some(SymbolRedirect::Forwarded))
&& let Some(current_id) = buffers.current_buffer_id()
&& let Some(info) = crate::buffer::buffer::lookup_buffer_slot_by_sym_id(sym_id)
{
let has_local = buffers
.get(current_id)
.map(|buf| info.local_flags_idx < 0 || buf.slot_local_flag(info.offset))
.unwrap_or(false);
if has_local {
let _ = buffers.set_buffer_local_property_by_sym_id(current_id, sym_id, value);
return Some(current_id);
}
let let_shadows = specpdl.iter().rev().any(|entry| {
matches!(
entry,
SpecBinding::LetDefault { sym_id: s, .. } | SpecBinding::LetLocal { sym_id: s, .. }
if *s == sym_id
)
});
if let_shadows {
buffers.set_buffer_default_slot(info, value);
return None;
}
let _ = buffers.set_buffer_local_property_by_sym_id(current_id, sym_id, value);
return Some(current_id);
}
if symbol_is_canonical
&& let Some(current_id) = buffers.current_buffer_id()
&& let Some(buf) = buffers.get(current_id)
&& buf.has_buffer_local_by_sym_id(sym_id)
{
let _ = buffers.set_buffer_local_property_by_sym_id(current_id, sym_id, value);
return Some(current_id);
}
obarray.set_symbol_value_id(sym_id, value);
None
}
pub(crate) fn makunbound_runtime_binding_in_state(
obarray: &mut Obarray,
buffers: &mut BufferManager,
_custom: &CustomManager,
_specpdl: &[SpecBinding],
sym_id: SymId,
) {
let symbol_is_canonical = super::builtins::is_canonical_symbol_id(sym_id);
if symbol_is_canonical
&& let Some(current_id) = buffers.current_buffer_id()
&& let Some(buf) = buffers.get(current_id)
&& buf.has_buffer_local_by_sym_id(sym_id)
{
let _ = buffers.set_buffer_local_void_property_by_sym_id(current_id, sym_id);
return;
}
let local_if_set = obarray
.blv(sym_id)
.map(|blv| blv.local_if_set)
.unwrap_or(false);
if symbol_is_canonical && local_if_set {
if let Some(current_id) = buffers.current_buffer_id() {
let _ = buffers.set_buffer_local_void_property_by_sym_id(current_id, sym_id);
return;
}
}
obarray.makunbound_id(sym_id);
}
impl Context {
pub(crate) fn materialize_public_evaluator_function_cells(&mut self) {
for name in super::subr_info::public_evaluator_subr_names() {
let sym_id = intern(name);
let name_id = symbol_name_id(sym_id);
let (min_args, max_args, dispatch_kind) =
super::subr_info::lookup_compat_subr_metadata(name, 0, None);
register_global_subr_entry(
sym_id,
SubrEntry {
function: None, min_args,
max_args,
dispatch_kind,
name_id,
},
);
self.obarray.intern(name);
self.obarray
.set_symbol_function_id(sym_id, Value::subr_from_sym_id(sym_id));
}
}
pub fn defsubr(
&mut self,
name: &str,
func: fn(&mut Context, Vec<Value>) -> EvalResult,
min_args: u16,
max_args: Option<u16>,
) {
self.defsubr_with_entry(
name,
crate::tagged::header::SubrFn::Many(func),
min_args,
max_args,
);
}
pub fn defsubr_slice(
&mut self,
name: &str,
func: fn(&mut Context, &[Value]) -> EvalResult,
min_args: u16,
max_args: Option<u16>,
) {
self.defsubr_with_entry(
name,
crate::tagged::header::SubrFn::ManySlice(func),
min_args,
max_args,
);
}
pub fn defsubr_0(&mut self, name: &str, func: fn(&mut Context) -> EvalResult) {
self.defsubr_with_entry(name, crate::tagged::header::SubrFn::A0(func), 0, Some(0));
}
pub fn defsubr_1(
&mut self,
name: &str,
func: fn(&mut Context, Value) -> EvalResult,
min_args: u16,
) {
self.defsubr_with_entry(
name,
crate::tagged::header::SubrFn::A1(func),
min_args,
Some(1),
);
}
pub fn defsubr_2(
&mut self,
name: &str,
func: fn(&mut Context, Value, Value) -> EvalResult,
min_args: u16,
) {
self.defsubr_with_entry(
name,
crate::tagged::header::SubrFn::A2(func),
min_args,
Some(2),
);
}
pub fn defsubr_3(
&mut self,
name: &str,
func: fn(&mut Context, Value, Value, Value) -> EvalResult,
min_args: u16,
) {
self.defsubr_with_entry(
name,
crate::tagged::header::SubrFn::A3(func),
min_args,
Some(3),
);
}
fn defsubr_with_entry(
&mut self,
name: &str,
func: crate::tagged::header::SubrFn,
min_args: u16,
max_args: Option<u16>,
) {
let (min_args, max_args, dispatch_kind) =
super::subr_info::lookup_compat_subr_metadata(name, min_args, max_args);
let sym_id = intern(name);
let name_id = symbol_name_id(sym_id);
register_global_subr_entry(
sym_id,
SubrEntry {
function: Some(func),
min_args,
max_args,
dispatch_kind,
name_id,
},
);
self.obarray.intern(name);
let should_install_public_subr =
self.obarray
.symbol_function_id(sym_id)
.is_none_or(|existing| {
matches!(
existing.kind(),
ValueKind::Subr(_) | ValueKind::Veclike(VecLikeType::Subr)
)
});
if should_install_public_subr {
self.obarray
.set_symbol_function(name, Value::subr_from_sym_id(sym_id));
}
}
pub fn dispatch_subr_value(&mut self, function: Value, args: Vec<Value>) -> Option<EvalResult> {
let sym_id = function.as_subr_id()?;
let wrong_arity_callee = Value::symbol(resolve_sym(sym_id));
self.dispatch_subr_value_internal(function, args.into(), wrong_arity_callee)
}
pub fn dispatch_subr_id(&mut self, sym_id: SymId, args: Vec<Value>) -> Option<EvalResult> {
let resolved = if lookup_global_subr_entry(sym_id).is_some() {
sym_id
} else {
let name_id = symbol_name_id(sym_id);
let canonical = crate::emacs_core::intern::canonical_symbol_for_name(name_id)?;
lookup_global_subr_entry(canonical)?;
canonical
};
let function = Value::subr_from_sym_id(resolved);
self.dispatch_subr_value(function, args)
}
pub fn dispatch_subr(&mut self, name: &str, args: Vec<Value>) -> Option<EvalResult> {
self.dispatch_subr_id(intern(name), args)
}
pub(crate) fn begin_eval_with_lexical_arg(
&mut self,
lexical_arg: Option<Value>,
) -> Result<ActiveEvalLexicalArgState, Flow> {
begin_eval_with_lexical_arg_in_state(
&mut self.obarray,
&mut self.lexenv,
&mut self.specpdl,
lexical_arg,
)
}
pub(crate) fn finish_eval_with_lexical_arg(&mut self, state: ActiveEvalLexicalArgState) {
finish_eval_with_lexical_arg_in_state(
&mut self.obarray,
&mut self.lexenv,
&mut self.specpdl,
state,
);
}
pub(crate) fn begin_macro_expansion_scope(&mut self) -> ActiveMacroExpansionScopeState {
self.macro_expansion_scope_depth += 1;
begin_macro_expansion_scope_in_state(
&mut self.obarray,
&mut self.specpdl,
&mut self.buffers,
&self.custom,
self.lexenv,
)
}
pub(crate) fn finish_macro_expansion_scope(&mut self, state: ActiveMacroExpansionScopeState) {
finish_macro_expansion_scope_in_state(
&mut self.obarray,
&mut self.specpdl,
&mut self.buffers,
&self.custom,
state,
);
self.macro_expansion_scope_depth = self.macro_expansion_scope_depth.saturating_sub(1);
}
pub(crate) fn kmacro_mut(&mut self) -> &mut KmacroManager {
&mut self.kmacro
}
pub(crate) fn gui_frame_creation_state(
&mut self,
) -> (
&mut FrameManager,
&mut BufferManager,
&mut Option<Box<dyn DisplayHost>>,
) {
(&mut self.frames, &mut self.buffers, &mut self.display_host)
}
pub(crate) fn recursive_command_loop_depth(&self) -> usize {
self.command_loop.recursive_depth.saturating_sub(1)
}
pub(crate) fn interactive_minibuffer_read_count(&self) -> u64 {
self.interactive_minibuffer_read_count
}
pub(crate) fn note_interactive_minibuffer_read(&mut self) {
self.interactive_minibuffer_read_count =
self.interactive_minibuffer_read_count.saturating_add(1);
}
fn sync_current_buffer_to_selected_window(&mut self) {
let Some(frame_id) = self.frames.selected_frame().map(|frame| frame.id) else {
return;
};
super::window_cmds::remember_selected_window_point_in_state(
&mut self.frames,
&self.buffers,
frame_id,
);
super::window_cmds::sync_selected_window_buffer_in_state(
&self.frames,
&mut self.buffers,
frame_id,
);
let _ = self.sync_current_buffer_runtime_state();
}
pub(crate) fn lexenv_assq_cached_in(&self, lexenv: Value, sym_id: SymId) -> Option<Value> {
let lexenv_bits = lexenv.bits();
let mut cache = self.lexenv_assq_cache.borrow_mut();
if let Some(cell) = cache.find(lexenv_bits, sym_id) {
return Some(cell);
}
let cell = lexenv_assq(lexenv, sym_id)?;
cache.push(LexenvAssqCacheEntry {
lexenv_bits,
symbol: sym_id,
cell,
});
Some(cell)
}
pub(crate) fn lexenv_lookup_cached_in(&self, lexenv: Value, sym_id: SymId) -> Option<Value> {
self.lexenv_assq_cached_in(lexenv, sym_id)
.map(|cell| cell.cons_cdr())
}
pub(crate) fn lexenv_declares_special_cached_in(&self, lexenv: Value, sym_id: SymId) -> bool {
let lexenv_bits = lexenv.bits();
let mut cache = self.lexenv_special_cache.borrow_mut();
if let Some(declared_special) = cache.find(lexenv_bits, sym_id) {
return declared_special;
}
let declared_special = lexenv_declares_special(lexenv, sym_id);
cache.push(LexenvSpecialCacheEntry {
lexenv_bits,
symbol: sym_id,
declared_special,
});
declared_special
}
pub(crate) fn assign_by_id(&mut self, sym_id: SymId, value: Value) {
let _ = self.assign_by_id_with_locus(sym_id, value);
}
pub(crate) fn assign_by_id_with_locus(
&mut self,
sym_id: SymId,
value: Value,
) -> Option<crate::buffer::BufferId> {
if self.lexical_binding() {
if let Some(cell_id) = self.lexenv_assq_cached_in(self.lexenv, sym_id) {
lexenv_set(cell_id, value);
return None;
}
}
let locus = set_runtime_binding(
&mut self.obarray,
&mut self.buffers,
&self.custom,
&self.specpdl,
sym_id,
value,
);
self.sync_cached_runtime_binding_by_id(sym_id, value);
self.sync_keyboard_runtime_binding_by_id(sym_id, value);
self.refresh_gc_runtime_settings_after_change_by_id(sym_id);
locus
}
pub(crate) fn assign(&mut self, name: &str, value: Value) {
self.assign_by_id(intern(name), value);
}
pub(crate) fn set_runtime_binding_by_id(
&mut self,
sym_id: SymId,
value: Value,
) -> Option<crate::buffer::BufferId> {
let locus = set_runtime_binding(
&mut self.obarray,
&mut self.buffers,
&self.custom,
&self.specpdl,
sym_id,
value,
);
self.sync_cached_runtime_binding_by_id(sym_id, value);
self.sync_keyboard_runtime_binding_by_id(sym_id, value);
self.refresh_gc_runtime_settings_after_change_by_id(sym_id);
locus
}
pub(crate) fn makunbound_runtime_binding_by_id(&mut self, sym_id: SymId) {
makunbound_runtime_binding_in_state(
&mut self.obarray,
&mut self.buffers,
&self.custom,
&[],
sym_id,
);
self.sync_cached_runtime_binding_by_id(sym_id, Value::NIL);
self.sync_keyboard_runtime_binding_by_id(sym_id, Value::NIL);
self.refresh_gc_runtime_settings_after_change_by_id(sym_id);
}
fn has_local_binding_by_id(&self, sym_id: SymId) -> bool {
self.lexenv_assq_cached_in(self.lexenv, sym_id).is_some()
|| self
.specpdl
.iter()
.rev()
.any(|entry| matches!(entry, SpecBinding::Let { sym_id: s, .. } if *s == sym_id))
}
pub(crate) fn visible_variable_value_or_nil(&self, name: &str) -> Value {
self.visible_variable_value_or_nil_by_id(intern(name))
}
pub(crate) fn visible_variable_value_or_nil_by_id(&self, sym_id: SymId) -> Value {
if let Some(value) = self.lexenv_lookup_cached_in(self.lexenv, sym_id) {
return value;
}
if let Ok(Some(value)) = self.visible_runtime_variable_value_by_id(sym_id) {
return value;
}
Value::NIL
}
pub(crate) fn visible_runtime_variable_value_by_id(
&self,
sym_id: SymId,
) -> Result<Option<Value>, Flow> {
let resolved = builtins::resolve_variable_alias_id_in_obarray(&self.obarray, sym_id)?;
Ok(self.visible_runtime_variable_value_by_id_resolved(resolved))
}
pub(crate) fn visible_runtime_variable_value_by_id_resolved(
&self,
resolved: SymId,
) -> Option<Value> {
let resolved_is_canonical = is_canonical_id(resolved);
use crate::emacs_core::symbol::SymbolRedirect;
if let Some(sym) = self.obarray.get_by_id(resolved) {
match sym.redirect() {
SymbolRedirect::Localized if resolved_is_canonical => {
if let Some(buf) = self.buffers.current_buffer() {
let target_buf = Value::make_buffer(buf.id);
if let Some(value) =
self.obarray
.read_localized(resolved, target_buf, buf.local_var_alist)
{
if value.is_unbound() {
return None;
}
return Some(value);
}
}
}
SymbolRedirect::Forwarded => {
if let Some(value) = self.forwarded_buffer_obj_value(sym) {
return Some(value);
}
}
SymbolRedirect::Plainval | SymbolRedirect::Varalias | SymbolRedirect::Localized => {
}
}
}
if resolved_is_canonical
&& resolved == buffer_undo_list_symbol()
&& let Some(buf) = self.buffers.current_buffer()
&& let Some(binding) = buf.get_buffer_local_binding_by_sym_id(resolved)
{
return binding.as_value();
}
if let Some(value) = self.obarray.symbol_value_id(resolved).copied() {
return Some(value);
}
if resolved_is_canonical && resolved == nil_symbol() {
return Some(Value::NIL);
}
if resolved_is_canonical && resolved == t_symbol() {
return Some(Value::T);
}
if is_keyword_id(resolved) {
return Some(Value::from_kw_id(resolved));
}
None
}
fn run_unlet_watchers(&mut self, bindings: &[(String, Value, Value)]) -> Result<(), Flow> {
for (name, _, restored_value) in bindings.iter().rev() {
self.run_variable_watchers(name, restored_value, &Value::NIL, "unlet")?;
}
Ok(())
}
pub(crate) fn run_variable_watchers_by_id(
&mut self,
sym_id: SymId,
new_value: &Value,
old_value: &Value,
operation: &str,
) -> Result<(), Flow> {
self.run_variable_watchers_by_id_with_where(
sym_id,
new_value,
old_value,
operation,
&Value::NIL,
)
}
pub(crate) fn run_variable_watchers_by_id_with_where(
&mut self,
sym_id: SymId,
new_value: &Value,
old_value: &Value,
operation: &str,
where_value: &Value,
) -> Result<(), Flow> {
if !self.watchers.has_watchers(sym_id) {
return Ok(());
}
let calls =
self.watchers
.notify_watchers(sym_id, new_value, old_value, operation, where_value);
for (callback, args) in calls {
let _ = self.apply(callback, args)?;
}
Ok(())
}
pub(crate) fn run_variable_watchers(
&mut self,
name: &str,
new_value: &Value,
old_value: &Value,
operation: &str,
) -> Result<(), Flow> {
self.run_variable_watchers_by_id(intern(name), new_value, old_value, operation)
}
pub(crate) fn run_variable_watchers_with_where(
&mut self,
name: &str,
new_value: &Value,
old_value: &Value,
operation: &str,
where_value: &Value,
) -> Result<(), Flow> {
self.run_variable_watchers_by_id_with_where(
intern(name),
new_value,
old_value,
operation,
where_value,
)
}
pub(crate) fn assign_with_watchers(
&mut self,
name: &str,
value: Value,
operation: &str,
) -> EvalResult {
self.assign_with_watchers_by_id(intern(name), value, operation)
}
pub(crate) fn assign_with_watchers_by_id(
&mut self,
sym_id: SymId,
value: Value,
operation: &str,
) -> EvalResult {
let where_value = self
.assign_by_id_with_locus(sym_id, value)
.map(Value::make_buffer)
.unwrap_or(Value::NIL);
self.run_variable_watchers_by_id_with_where(
sym_id,
&value,
&Value::NIL,
operation,
&where_value,
)?;
Ok(value)
}
}
fn format_startup_value(value: Option<&Value>) -> String {
value
.map(super::print::print_value)
.unwrap_or_else(|| "<unbound>".to_string())
}
fn value_list_to_values(list: &Value) -> LispArgVec {
let mut result = LispArgVec::new();
let mut cursor = *list;
while cursor.is_cons() {
result.push(cursor.cons_car());
cursor = cursor.cons_cdr();
}
result
}
#[cfg(test)]
#[path = "eval_test.rs"]
mod tests;
fn runtime_string_value(value: Value) -> String {
value
.as_runtime_string_owned()
.expect("ValueKind::String must carry LispString payload")
}