use std::collections::HashMap;
use super::intern::SymId;
use super::symbol::Obarray;
use super::value::{Value, ValueKind, VecLikeType};
use crate::gc_trace::GcTrace;
#[derive(Clone, Debug)]
pub struct VariableWatcher {
pub callback: Value,
}
pub struct VariableWatcherList {
watchers: HashMap<SymId, Vec<VariableWatcher>>,
}
impl VariableWatcherList {
pub fn new() -> Self {
Self {
watchers: HashMap::new(),
}
}
pub fn add_watcher(&mut self, var_id: SymId, callback: Value) {
let entry = self.watchers.entry(var_id).or_default();
let already_exists = entry
.iter()
.any(|w| watcher_callback_matches(&w.callback, &callback));
if !already_exists {
entry.push(VariableWatcher { callback });
}
}
pub fn remove_watcher(&mut self, var_id: SymId, callback: &Value) {
if let Some(list) = self.watchers.get_mut(&var_id) {
list.retain(|w| !watcher_callback_matches(&w.callback, callback));
if list.is_empty() {
self.watchers.remove(&var_id);
}
}
}
pub fn clear_watchers(&mut self, var_id: SymId) {
self.watchers.remove(&var_id);
}
pub fn has_watchers(&self, var_id: SymId) -> bool {
self.watchers
.get(&var_id)
.is_some_and(|list| !list.is_empty())
}
pub fn get_watchers(&self, var_id: SymId) -> Vec<Value> {
self.watchers
.get(&var_id)
.map(|list| list.iter().map(|watcher| watcher.callback).collect())
.unwrap_or_default()
}
pub fn notify_watchers(
&self,
var_id: SymId,
new_val: &Value,
_old_val: &Value,
operation: &str,
where_val: &Value,
) -> Vec<(Value, Vec<Value>)> {
let mut calls = Vec::new();
if let Some(list) = self.watchers.get(&var_id) {
for watcher in list {
let args = vec![
Value::from_sym_id(var_id),
*new_val,
Value::symbol(operation),
*where_val,
];
calls.push((watcher.callback, args));
}
}
calls
}
pub(crate) fn dump_watchers(&self) -> &HashMap<SymId, Vec<VariableWatcher>> {
&self.watchers
}
pub(crate) fn from_dump(watchers: HashMap<SymId, Vec<VariableWatcher>>) -> Self {
Self { watchers }
}
}
fn watcher_callback_matches(registered: &Value, candidate: &Value) -> bool {
if registered == candidate {
return true;
}
match (registered.kind(), candidate.kind()) {
(ValueKind::Veclike(VecLikeType::Lambda), ValueKind::Veclike(VecLikeType::Lambda))
| (ValueKind::Veclike(VecLikeType::Macro), ValueKind::Veclike(VecLikeType::Macro)) => {
lambda_data_matches(registered, candidate)
}
_ => false,
}
}
fn lambda_data_matches(left: &Value, right: &Value) -> bool {
let (Some(left_params), Some(right_params)) = (left.closure_params(), right.closure_params())
else {
return false;
};
left_params.required == right_params.required
&& left_params.optional == right_params.optional
&& left_params.rest == right_params.rest
&& left
.closure_body_value()
.zip(right.closure_body_value())
.is_some_and(|(left_body, right_body)| {
super::value::equal_value(&left_body, &right_body, 0)
})
&& lex_envs_equal(
&left.closure_env().unwrap_or(None),
&right.closure_env().unwrap_or(None),
)
&& left.closure_docstring().flatten() == right.closure_docstring().flatten()
}
fn lex_envs_equal(a: &Option<super::value::Value>, b: &Option<super::value::Value>) -> bool {
a == b
}
impl Default for VariableWatcherList {
fn default() -> Self {
Self::new()
}
}
impl GcTrace for VariableWatcherList {
fn trace_roots(&self, roots: &mut Vec<Value>) {
for watcher_list in self.watchers.values() {
for watcher in watcher_list {
roots.push(watcher.callback);
}
}
}
}
use super::error::{EvalResult, Flow, signal};
fn expect_args(name: &str, args: &[Value], n: usize) -> Result<(), Flow> {
if args.len() != n {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
pub(crate) fn builtin_add_variable_watcher(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("add-variable-watcher", &args, 2)?;
let symbol = super::builtins::symbols::expect_symbol_id(&args[0])?;
let resolved =
super::builtins::symbols::resolve_variable_alias_id_in_obarray(&eval.obarray, symbol)?;
let callback = args[1];
eval.watchers.add_watcher(resolved, callback);
Ok(Value::NIL)
}
pub(crate) fn builtin_remove_variable_watcher(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("remove-variable-watcher", &args, 2)?;
let symbol = super::builtins::symbols::expect_symbol_id(&args[0])?;
let resolved =
super::builtins::symbols::resolve_variable_alias_id_in_obarray(&eval.obarray, symbol)?;
let callback = args[1];
eval.watchers.remove_watcher(resolved, &callback);
Ok(Value::NIL)
}
pub(crate) fn builtin_get_variable_watchers(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("get-variable-watchers", &args, 1)?;
let symbol = super::builtins::symbols::expect_symbol_id(&args[0])?;
let resolved =
super::builtins::symbols::resolve_variable_alias_id_in_obarray(&eval.obarray, symbol)?;
Ok(Value::list(eval.watchers.get_watchers(resolved)))
}
#[cfg(test)]
#[path = "advice_test.rs"]
mod tests;