Skip to main content

conjure_cp_core/ast/
symbol_table.rs

1//! The symbol table.
2//!
3//! See the item documentation for [`SymbolTable`] for more details.
4
5/// Sentinel for an uncached symbol-table context hash.
6const NO_CONTEXT_HASH: u64 = 0;
7
8fn default_context_hash_cache() -> AtomicU64 {
9    AtomicU64::new(NO_CONTEXT_HASH)
10}
11
12use crate::representation::ReprId;
13use crate::representation::Representation;
14use std::any::TypeId;
15
16use std::collections::BTreeMap;
17use std::collections::VecDeque;
18use std::hash::{DefaultHasher, Hash, Hasher};
19use std::sync::Arc;
20use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
21
22use super::comprehension::Comprehension;
23use super::declaration::declaration_content_generation;
24use super::serde::{AsId, DefaultWithId, HasId, IdPtr, ObjId, PtrAsInner};
25use super::{
26    DeclarationPtr, DomainPtr, Expression, GroundDomain, Model, Moo, Name, ReturnType, Typeable,
27};
28use itertools::{Itertools as _, izip};
29use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
30use serde::{Deserialize, Serialize};
31use serde_with::serde_as;
32use tracing::trace;
33use uniplate::{Biplate, Tree, Uniplate};
34
35use indexmap::IndexMap;
36use indexmap::map::Entry;
37
38/// Global counter of symbol tables.
39/// Note that the counter is shared between all threads
40/// Thus, when running multiple models in parallel, IDs may
41/// be different with every run depending on scheduling order
42static SYMBOL_TABLE_ID_COUNTER: AtomicU32 = const { AtomicU32::new(0) };
43
44#[derive(Debug, Clone, PartialEq, Eq, Hash)]
45pub struct SymbolTablePtr
46where
47    Self: Send + Sync,
48{
49    inner: Arc<SymbolTablePtrInner>,
50}
51
52impl SymbolTablePtr {
53    /// Create an empty new [SymbolTable] and return a shared pointer to it
54    pub fn new() -> Self {
55        Self::new_with_data(SymbolTable::new())
56    }
57
58    /// Create an empty new [SymbolTable] with the given parent and return a shared pointer to it
59    pub fn with_parent(symbols: SymbolTablePtr) -> Self {
60        Self::new_with_data(SymbolTable::with_parent(symbols))
61    }
62
63    fn new_with_data(data: SymbolTable) -> Self {
64        let object_id = SYMBOL_TABLE_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
65        let id = ObjId {
66            object_id,
67            type_name: SymbolTablePtr::TYPE_NAME.into(),
68        };
69        Self::new_with_id_and_data(id, data)
70    }
71
72    fn new_with_id_and_data(id: ObjId, data: SymbolTable) -> Self {
73        Self {
74            inner: Arc::new(SymbolTablePtrInner {
75                id,
76                value: RwLock::new(data),
77            }),
78        }
79    }
80
81    /// Read the underlying symbol table.
82    /// This will block the current thread until a read lock can be acquired.
83    ///
84    /// # WARNING
85    ///
86    /// - If the current thread already holds a lock over this table, this may deadlock.
87    pub fn read(&self) -> RwLockReadGuard<'_, SymbolTable> {
88        self.inner.value.read()
89    }
90
91    /// Mutate the underlying symbol table.
92    /// This will block the current thread until an exclusive write lock can be acquired.
93    ///
94    /// # WARNING
95    ///
96    /// - If the current thread already holds a lock over this table, this may deadlock.
97    /// - Trying to acquire any other lock until the write lock is released will cause a deadlock.
98    /// - This will mutate the underlying data, which may be shared between other `SymbolTablePtr`s.
99    ///   Make sure that this is what you want.
100    ///
101    /// To create a separate copy of the table, see [SymbolTablePtr::detach].
102    ///
103    pub fn write(&self) -> RwLockWriteGuard<'_, SymbolTable> {
104        self.inner.value.write()
105    }
106
107    /// Create a new symbol table with the same contents as this one, but a new ID,
108    /// and return a pointer to it.
109    pub fn detach(&self) -> Self {
110        Self::new_with_data(self.read().clone())
111    }
112}
113
114impl Default for SymbolTablePtr {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120impl HasId for SymbolTablePtr {
121    const TYPE_NAME: &'static str = "SymbolTable";
122
123    fn id(&self) -> ObjId {
124        self.inner.id.clone()
125    }
126}
127
128impl DefaultWithId for SymbolTablePtr {
129    fn default_with_id(id: ObjId) -> Self {
130        Self::new_with_id_and_data(id, SymbolTable::default())
131    }
132}
133
134impl IdPtr for SymbolTablePtr {
135    type Data = SymbolTable;
136
137    fn get_data(&self) -> Self::Data {
138        self.read().clone()
139    }
140
141    fn with_id_and_data(id: ObjId, data: Self::Data) -> Self {
142        Self::new_with_id_and_data(id, data)
143    }
144}
145
146// TODO: this code is almost exactly copied from [DeclarationPtr].
147//       It should be possible to eliminate the duplication...
148//       Perhaps by merging SymbolTablePtr and DeclarationPtr together?
149//       (Alternatively, a macro?)
150
151impl Uniplate for SymbolTablePtr {
152    fn uniplate(&self) -> (Tree<Self>, Box<dyn Fn(Tree<Self>) -> Self>) {
153        let symtab = self.read();
154        let (tree, recons) = Biplate::<SymbolTablePtr>::biplate(&symtab as &SymbolTable);
155
156        let self2 = self.clone();
157        (
158            tree,
159            Box::new(move |x| {
160                let self3 = self2.clone();
161                *(self3.write()) = recons(x);
162                self3
163            }),
164        )
165    }
166}
167
168impl<To> Biplate<To> for SymbolTablePtr
169where
170    SymbolTable: Biplate<To>,
171    To: Uniplate,
172{
173    fn biplate(&self) -> (Tree<To>, Box<dyn Fn(Tree<To>) -> Self>) {
174        if TypeId::of::<To>() == TypeId::of::<Self>() {
175            unsafe {
176                let self_as_to = std::mem::transmute::<&Self, &To>(self).clone();
177                (
178                    Tree::One(self_as_to),
179                    Box::new(move |x| {
180                        let Tree::One(x) = x else { panic!() };
181
182                        let x_as_self = std::mem::transmute::<&To, &Self>(&x);
183                        x_as_self.clone()
184                    }),
185                )
186            }
187        } else {
188            // call biplate on the enclosed declaration
189            let decl = self.read();
190            let (tree, recons) = Biplate::<To>::biplate(&decl as &SymbolTable);
191
192            let self2 = self.clone();
193            (
194                tree,
195                Box::new(move |x| {
196                    let self3 = self2.clone();
197                    *(self3.write()) = recons(x);
198                    self3
199                }),
200            )
201        }
202    }
203}
204
205#[derive(Debug)]
206struct SymbolTablePtrInner {
207    id: ObjId,
208    value: RwLock<SymbolTable>,
209}
210
211impl Hash for SymbolTablePtrInner {
212    fn hash<H: Hasher>(&self, state: &mut H) {
213        // Identity hash. Symbol table content is mutable and is hashed by `SymbolTable::context_hash`.
214        self.id.hash(state);
215    }
216}
217
218impl PartialEq for SymbolTablePtrInner {
219    fn eq(&self, other: &Self) -> bool {
220        self.id == other.id
221    }
222}
223
224impl Eq for SymbolTablePtrInner {}
225
226/// The global symbol table, mapping names to their definitions.
227///
228/// Names in the symbol table are unique, including between different types of object stored in the
229/// symbol table. For example, you cannot have a letting and decision variable with the same name.
230///
231/// # Symbol Kinds
232///
233/// The symbol table tracks the following types of symbol:
234///
235/// ## Decision Variables
236///
237/// ```text
238/// find NAME: DOMAIN
239/// ```
240///
241/// See [`DecisionVariable`](super::DecisionVariable).
242///
243/// ## Lettings
244///
245/// Lettings define constants, of which there are two types:
246///
247///   + **Constant values**: `letting val be A`, where A is an [`Expression`].
248///
249///     A can be any integer, boolean, or matrix expression.
250///     A can include references to other lettings, model parameters, and, unlike Savile Row,
251///     decision variables.
252///
253///   + **Constant domains**: `letting Domain be domain D`, where D is a [`Domain`].
254///
255///     D can include references to other lettings and model parameters, and, unlike Savile Row,
256///     decision variables.
257///
258/// Unless otherwise stated, these follow the semantics specified in section 2.2.2 of the Savile
259/// Row manual (version 1.9.1 at time of writing).
260#[serde_as]
261#[derive(Debug, Serialize, Deserialize)]
262pub struct SymbolTable {
263    #[serde_as(as = "Vec<(_,PtrAsInner)>")]
264    table: IndexMap<Name, DeclarationPtr>,
265
266    #[serde_as(as = "Option<AsId>")]
267    parent: Option<SymbolTablePtr>,
268
269    next_machine_name: i32,
270
271    #[serde(default, skip_serializing)]
272    local_bindings_xor: u64,
273
274    #[serde(default, skip_serializing)]
275    /// Declaration generation represented by `local_bindings_xor`.
276    local_bindings_generation: u64,
277
278    #[serde(default = "default_context_hash_cache", skip_serializing)]
279    context_hash_cache: AtomicU64,
280
281    #[serde(default = "default_context_hash_cache", skip_serializing)]
282    /// Declaration generation represented by `context_hash_cache`.
283    context_hash_generation: AtomicU64,
284}
285
286impl Clone for SymbolTable {
287    fn clone(&self) -> Self {
288        Self {
289            table: self.table.clone(),
290            parent: self.parent.clone(),
291            next_machine_name: self.next_machine_name,
292            local_bindings_xor: self.local_bindings_xor,
293            local_bindings_generation: self.local_bindings_generation,
294            context_hash_cache: AtomicU64::new(NO_CONTEXT_HASH),
295            context_hash_generation: AtomicU64::new(0),
296        }
297    }
298}
299
300impl PartialEq for SymbolTable {
301    fn eq(&self, other: &Self) -> bool {
302        self.table == other.table
303            && self.parent == other.parent
304            && self.next_machine_name == other.next_machine_name
305    }
306}
307
308impl Eq for SymbolTable {}
309
310impl SymbolTable {
311    /// Creates an empty symbol table.
312    pub fn new() -> SymbolTable {
313        SymbolTable::new_inner(None)
314    }
315
316    /// Creates an empty symbol table with the given parent.
317    pub fn with_parent(parent: SymbolTablePtr) -> SymbolTable {
318        SymbolTable::new_inner(Some(parent))
319    }
320
321    fn new_inner(parent: Option<SymbolTablePtr>) -> SymbolTable {
322        let id = SYMBOL_TABLE_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
323        trace!(
324            "new symbol table: id = {id}  parent_id = {}",
325            parent
326                .as_ref()
327                .map(|x| x.id().to_string())
328                .unwrap_or(String::from("none"))
329        );
330        SymbolTable {
331            table: IndexMap::new(),
332            next_machine_name: 0,
333            parent,
334            local_bindings_xor: 0,
335            local_bindings_generation: declaration_content_generation(),
336            context_hash_cache: AtomicU64::new(NO_CONTEXT_HASH),
337            context_hash_generation: AtomicU64::new(0),
338        }
339    }
340
341    fn binding_contribution(name: &Name, declaration: &DeclarationPtr) -> u64 {
342        let mut hasher = DefaultHasher::new();
343        (name.clone(), declaration.content_hash()).hash(&mut hasher);
344        hasher.finish()
345    }
346
347    fn finalize_context_hash(bindings_xor: u64, binding_count: usize) -> u64 {
348        let mut hasher = DefaultHasher::new();
349        binding_count.hash(&mut hasher);
350        bindings_xor.hash(&mut hasher);
351        hasher.finish()
352    }
353
354    fn recompute_local_bindings_xor(&mut self) {
355        self.local_bindings_xor = self.table.iter().fold(0u64, |acc, (name, declaration)| {
356            acc ^ Self::binding_contribution(name, declaration)
357        });
358        self.local_bindings_generation = declaration_content_generation();
359    }
360
361    /// Recomputes the incremental binding hash after shared declaration mutation.
362    fn ensure_local_binding_hashes_current(&mut self) {
363        if self.local_bindings_generation != declaration_content_generation() {
364            self.recompute_local_bindings_xor();
365        }
366    }
367
368    pub(crate) fn refresh_local_binding_hashes(&mut self) {
369        self.recompute_local_bindings_xor();
370        self.invalidate_context_hash_cache();
371    }
372
373    fn xor_binding_in(&mut self, name: &Name, declaration: &DeclarationPtr) {
374        self.local_bindings_xor ^= Self::binding_contribution(name, declaration);
375    }
376
377    fn xor_binding_out(&mut self, name: &Name, declaration: &DeclarationPtr) {
378        self.local_bindings_xor ^= Self::binding_contribution(name, declaration);
379    }
380
381    pub(crate) fn invalidate_context_hash_cache(&mut self) {
382        self.context_hash_cache
383            .store(NO_CONTEXT_HASH, Ordering::Relaxed);
384        self.context_hash_generation.store(0, Ordering::Relaxed);
385    }
386
387    /// Returns a cached hash of all declarations visible from this scope.
388    ///
389    /// This hashes declaration values, not pointer identity, so rewrite caches remain valid across
390    /// equivalent symbol states.
391    pub fn context_hash(&self) -> u64 {
392        let declaration_generation = declaration_content_generation();
393        let cached_generation = self.context_hash_generation.load(Ordering::Acquire);
394        let cached = self.context_hash_cache.load(Ordering::Relaxed);
395        if cached != NO_CONTEXT_HASH && cached_generation == declaration_generation {
396            return cached;
397        }
398
399        loop {
400            let generation_before = declaration_content_generation();
401            let hash = if self.parent.is_none() {
402                let bindings_xor = if self.local_bindings_generation == generation_before {
403                    self.local_bindings_xor
404                } else {
405                    self.table.iter().fold(0u64, |acc, (name, declaration)| {
406                        acc ^ Self::binding_contribution(name, declaration)
407                    })
408                };
409                Self::finalize_context_hash(bindings_xor, self.table.len())
410            } else {
411                self.compute_scoped_context_hash()
412            };
413            let generation_after = declaration_content_generation();
414            if generation_before == generation_after {
415                self.context_hash_cache.store(hash, Ordering::Relaxed);
416                self.context_hash_generation
417                    .store(generation_after, Ordering::Release);
418                break hash;
419            }
420        }
421    }
422
423    fn compute_scoped_context_hash(&self) -> u64 {
424        let mut hasher = DefaultHasher::new();
425        let mut declarations: BTreeMap<Name, u64> = BTreeMap::new();
426
427        for (name, declaration) in self.iter_local() {
428            declarations.insert(name.clone(), declaration.content_hash());
429        }
430
431        let mut parent = self.parent.clone();
432        while let Some(parent_ptr) = parent.take() {
433            let guard = parent_ptr.read();
434            for (name, declaration) in guard.iter_local() {
435                declarations
436                    .entry(name.clone())
437                    .or_insert_with(|| declaration.content_hash());
438            }
439            parent.clone_from(guard.parent());
440        }
441
442        declarations.len().hash(&mut hasher);
443        for declaration in declarations {
444            declaration.hash(&mut hasher);
445        }
446        hasher.finish()
447    }
448
449    /// Looks up the declaration with the given name in the current scope only.
450    ///
451    /// Returns `None` if there is no declaration with that name in the current scope.
452    pub fn lookup_local(&self, name: &Name) -> Option<DeclarationPtr> {
453        self.table.get(name).cloned()
454    }
455
456    /// Looks up the declaration with the given name, checking all enclosing scopes.
457    ///
458    /// Returns `None` if there is no declaration with that name in scope.
459    pub fn lookup(&self, name: &Name) -> Option<DeclarationPtr> {
460        self.lookup_local(name).or_else(|| {
461            self.parent
462                .as_ref()
463                .and_then(|parent| parent.read().lookup(name))
464        })
465    }
466
467    /// Inserts a declaration into the symbol table.
468    ///
469    /// Returns `None` if there is already a symbol with this name in the local scope.
470    pub fn insert(&mut self, declaration: DeclarationPtr) -> Option<()> {
471        self.ensure_local_binding_hashes_current();
472        let name = declaration.name().clone();
473        let contribution = Self::binding_contribution(&name, &declaration);
474        if let Entry::Vacant(e) = self.table.entry(name) {
475            self.local_bindings_xor ^= contribution;
476            e.insert(declaration);
477            self.invalidate_context_hash_cache();
478            Some(())
479        } else {
480            None
481        }
482    }
483
484    /// Updates or adds a declaration in the immediate local scope.
485    pub fn update_insert(&mut self, declaration: DeclarationPtr) {
486        self.ensure_local_binding_hashes_current();
487        let name = declaration.name().clone();
488        if let Some(existing) = self.table.get(&name).cloned() {
489            self.xor_binding_out(&name, &existing);
490        }
491        self.xor_binding_in(&name, &declaration);
492        self.table.insert(name, declaration);
493        self.invalidate_context_hash_cache();
494    }
495
496    /// Looks up the return type for name if it has one and is in scope.
497    pub fn return_type(&self, name: &Name) -> Option<ReturnType> {
498        self.lookup(name).map(|x| x.return_type())
499    }
500
501    /// Looks up the return type for name if has one and is in the local scope.
502    pub fn return_type_local(&self, name: &Name) -> Option<ReturnType> {
503        self.lookup_local(name).map(|x| x.return_type())
504    }
505
506    /// Looks up the domain of name if it has one and is in scope.
507    ///
508    /// This method can return domain references: if a ground domain is always required, use
509    /// [`SymbolTable::resolve_domain`].
510    pub fn domain(&self, name: &Name) -> Option<DomainPtr> {
511        if let Name::WithRepresentation(name, _) = name {
512            self.lookup(name)?.domain()
513        } else {
514            self.lookup(name)?.domain()
515        }
516    }
517
518    /// Looks up the domain of name, resolving domain references to ground domains.
519    ///
520    /// See [`SymbolTable::domain`].
521    pub fn resolve_domain(&self, name: &Name) -> Option<Moo<GroundDomain>> {
522        self.domain(name)?.resolve().ok()
523    }
524
525    /// Iterates over entries in the LOCAL symbol table.
526    pub fn into_iter_local(self) -> impl Iterator<Item = (Name, DeclarationPtr)> {
527        self.table.into_iter()
528    }
529
530    /// Iterates over entries in the LOCAL symbol table, by reference.
531    pub fn iter_local(&self) -> impl Iterator<Item = (&Name, &DeclarationPtr)> {
532        self.table.iter()
533    }
534
535    /// Iterates over entries in the LOCAL symbol table, by reference.
536    pub fn iter_local_mut(&mut self) -> impl Iterator<Item = (&Name, &mut DeclarationPtr)> {
537        self.table.iter_mut()
538    }
539
540    /// Extends the symbol table with the given symbol table, updating the gensym counter if
541    /// necessary.
542    pub fn extend(&mut self, other: SymbolTable) {
543        self.ensure_local_binding_hashes_current();
544        self.next_machine_name = self.next_machine_name.max(other.next_machine_name);
545
546        for (name, declaration) in &other.table {
547            if let Some(existing) = self.table.get(name).cloned() {
548                self.xor_binding_out(name, &existing);
549            }
550            self.xor_binding_in(name, declaration);
551        }
552        self.table.extend(other.table);
553        self.invalidate_context_hash_cache();
554    }
555
556    /// Retains only local bindings that are new or changed relative to `baseline`.
557    ///
558    /// Rules traditionally build their effects by cloning the model symbol table and mutating the
559    /// clone. Compacting that snapshot before application turns it into a small effect delta, so
560    /// impact analysis and `extend` do not repeatedly process every unchanged declaration.
561    pub(crate) fn retain_local_changes_from(&mut self, baseline: &SymbolTable) {
562        self.table.retain(|name, declaration| {
563            baseline
564                .table
565                .get(name)
566                .is_none_or(|old| !declaration.content_eq(old))
567        });
568        self.recompute_local_bindings_xor();
569        self.invalidate_context_hash_cache();
570    }
571
572    /// Creates a new [`DeclarationKind::Find`] declaration in this symbol table with a unique name.
573    ///
574    /// Prefer [`Self::gen_find_auxiliary`] for rewriter-introduced auxiliaries that must not be
575    /// branched on. Use this only when a generated searchable find is intentionally required.
576    pub fn gen_find(&mut self, domain: &DomainPtr) -> DeclarationPtr {
577        let decl = DeclarationPtr::new_find(self.gen_sym(), domain.clone());
578        self.insert(decl.clone());
579        decl
580    }
581
582    /// Creates a new auxiliary find declaration in this symbol table with a unique name.
583    ///
584    /// See [`DeclarationPtr::new_find_auxiliary`].
585    pub fn gen_find_auxiliary(&mut self, domain: &DomainPtr) -> DeclarationPtr {
586        let decl = DeclarationPtr::new_find_auxiliary(self.gen_sym(), domain.clone());
587        self.insert(decl.clone());
588        decl
589    }
590
591    // Reserves a unique machine name in the symbol table
592    pub fn gen_sym(&mut self) -> Name {
593        let num = self.next_machine_name;
594        self.next_machine_name += 1;
595        Name::Machine(num)
596    }
597
598    /// Gets the parent of this symbol table as a mutable reference.
599    ///
600    /// This function provides no sanity checks.
601    pub fn parent_mut_unchecked(&mut self) -> &mut Option<SymbolTablePtr> {
602        &mut self.parent
603    }
604
605    /// Gets the parent of this symbol table.
606    pub fn parent(&self) -> &Option<SymbolTablePtr> {
607        &self.parent
608    }
609
610    /// Gets the representation `representation` for `name`.
611    ///
612    /// # Returns
613    ///
614    /// + `None` if `name` does not exist, is not a decision variable, or does not have that representation.
615    pub fn get_representation(
616        &self,
617        name: &Name,
618        representation: &[ReprId],
619    ) -> Option<Vec<Box<dyn Representation>>> {
620        // TODO: move representation stuff to declaration / variable to avoid cloning? (we have to
621        // move inside of an rc here, so cannot return borrows)
622        //
623        // Also would prevent constant "does exist" "is var" checks.
624        //
625        // The reason it is not there now is because I'm getting serde issues...
626        //
627        // Also might run into issues putting get_or_add into declaration/variable, as that
628        // requires us to mutably borrow both the symbol table, and the variable inside the symbol
629        // table..
630
631        let decl = self.lookup(name)?;
632        let var = &decl.as_find()?;
633
634        var.representations
635            .iter()
636            .find(|x| &x.iter().map(|r| r.repr_id()).collect_vec()[..] == representation)
637            .cloned()
638    }
639
640    /// Gets all initialised representations for `name`.
641    ///
642    /// # Returns
643    ///
644    /// + `None` if `name` does not exist, or is not a decision variable.
645    pub fn representations_for(&self, name: &Name) -> Option<Vec<Vec<Box<dyn Representation>>>> {
646        let decl = self.lookup(name)?;
647        decl.as_find().map(|x| x.representations.clone())
648    }
649}
650
651impl IntoIterator for SymbolTable {
652    type Item = (Name, DeclarationPtr);
653
654    type IntoIter = SymbolTableIter;
655
656    /// Iterates over symbol table entries in scope.
657    fn into_iter(self) -> Self::IntoIter {
658        SymbolTableIter {
659            inner: self.table.into_iter(),
660            parent: self.parent,
661        }
662    }
663}
664
665/// Iterator over all symbol table entries in scope.
666pub struct SymbolTableIter {
667    // iterator over the current scopes' btreemap
668    inner: indexmap::map::IntoIter<Name, DeclarationPtr>,
669
670    // the parent scope
671    parent: Option<SymbolTablePtr>,
672}
673
674impl Iterator for SymbolTableIter {
675    type Item = (Name, DeclarationPtr);
676
677    fn next(&mut self) -> Option<Self::Item> {
678        let mut val = self.inner.next();
679
680        // Go up the tree until we find a parent symbol table with declarations to iterate over.
681        //
682        // Note that the parent symbol table may be empty - this is why this is a loop!
683        while val.is_none() {
684            let parent = self.parent.clone()?;
685
686            let guard = parent.read();
687            self.inner = guard.table.clone().into_iter();
688            self.parent.clone_from(&guard.parent);
689
690            val = self.inner.next();
691        }
692
693        val
694    }
695}
696
697impl Default for SymbolTable {
698    fn default() -> Self {
699        Self::new_inner(None)
700    }
701}
702
703// TODO: if we could override `Uniplate` impl but still derive `Biplate` instances,
704//       we could remove some of this manual code
705impl Uniplate for SymbolTable {
706    fn uniplate(&self) -> (Tree<Self>, Box<dyn Fn(Tree<Self>) -> Self>) {
707        // do not recurse up parents, that would be weird?
708        let self2 = self.clone();
709        (Tree::Zero, Box::new(move |_| self2.clone()))
710    }
711}
712
713impl Biplate<SymbolTablePtr> for SymbolTable {
714    fn biplate(
715        &self,
716    ) -> (
717        Tree<SymbolTablePtr>,
718        Box<dyn Fn(Tree<SymbolTablePtr>) -> Self>,
719    ) {
720        let self2 = self.clone();
721        (Tree::Zero, Box::new(move |_| self2.clone()))
722    }
723}
724
725impl Biplate<Expression> for SymbolTable {
726    fn biplate(&self) -> (Tree<Expression>, Box<dyn Fn(Tree<Expression>) -> Self>) {
727        let (child_trees, ctxs): (VecDeque<_>, Vec<_>) = self
728            .table
729            .values()
730            .map(Biplate::<Expression>::biplate)
731            .unzip();
732
733        let tree = Tree::Many(child_trees);
734
735        let self2 = self.clone();
736        let ctx = Box::new(move |tree| {
737            let Tree::Many(exprs) = tree else {
738                panic!("unexpected children structure");
739            };
740
741            let mut self3 = self2.clone();
742            let self3_iter = self3.table.iter_mut();
743            for (ctx, tree, (_, decl)) in izip!(&ctxs, exprs, self3_iter) {
744                // update declaration inside the pointer instead of creating a new one, so all
745                // things referencing this keep referencing this.
746                *decl = ctx(tree)
747            }
748
749            self3
750        });
751
752        (tree, ctx)
753    }
754}
755
756impl Biplate<Comprehension> for SymbolTable {
757    fn biplate(
758        &self,
759    ) -> (
760        Tree<Comprehension>,
761        Box<dyn Fn(Tree<Comprehension>) -> Self>,
762    ) {
763        let (expr_tree, expr_ctx) = <SymbolTable as Biplate<Expression>>::biplate(self);
764
765        let (exprs, recons_expr_tree) = expr_tree.list();
766
767        let (comprehension_tree, comprehension_ctx) =
768            <VecDeque<Expression> as Biplate<Comprehension>>::biplate(&exprs);
769
770        let ctx = Box::new(move |x| {
771            // 1. turn comprehension tree into a list of expressions
772            let exprs = comprehension_ctx(x);
773
774            // 2. turn list of expressions into an expression tree
775            let expr_tree = recons_expr_tree(exprs);
776
777            // 3. turn expression tree into a symbol table
778            expr_ctx(expr_tree)
779        });
780
781        (comprehension_tree, ctx)
782    }
783}
784
785impl Biplate<Model> for SymbolTable {
786    // walk into expressions
787    fn biplate(&self) -> (Tree<Model>, Box<dyn Fn(Tree<Model>) -> Self>) {
788        let (expr_tree, expr_ctx) = <SymbolTable as Biplate<Expression>>::biplate(self);
789
790        let (exprs, recons_expr_tree) = expr_tree.list();
791
792        let (submodel_tree, submodel_ctx) =
793            <VecDeque<Expression> as Biplate<Model>>::biplate(&exprs);
794
795        let ctx = Box::new(move |x| {
796            // 1. turn submodel tree into a list of expressions
797            let exprs = submodel_ctx(x);
798
799            // 2. turn list of expressions into an expression tree
800            let expr_tree = recons_expr_tree(exprs);
801
802            // 3. turn expression tree into a symbol table
803            expr_ctx(expr_tree)
804        });
805        (submodel_tree, ctx)
806    }
807}
808
809#[cfg(test)]
810mod tests {
811    use std::hash::{DefaultHasher, Hash, Hasher};
812
813    use super::*;
814    use crate::ast::{Domain, Range, Reference};
815
816    #[test]
817    fn reference_observes_in_place_domain_update() {
818        let old_domain = Domain::int(vec![Range::Bounded(1, 3)]);
819        let new_domain = Domain::int(vec![Range::Bounded(1, 2)]);
820        let mut declaration = DeclarationPtr::new_find(Name::user("x"), old_domain);
821        let reference = Reference::new(declaration.clone());
822        let original_id = reference.id();
823
824        declaration.as_find_mut().unwrap().domain = new_domain.clone();
825
826        assert_eq!(reference.id(), original_id);
827        assert_eq!(reference.domain(), Some(new_domain));
828    }
829
830    #[test]
831    fn context_hash_observes_shared_declaration_update() {
832        let mut symbols = SymbolTable::new();
833        let mut declaration =
834            DeclarationPtr::new_find(Name::user("x"), Domain::int(vec![Range::Bounded(1, 3)]));
835        symbols.insert(declaration.clone()).unwrap();
836        let before = symbols.context_hash();
837
838        declaration.as_find_mut().unwrap().domain = Domain::int(vec![Range::Bounded(1, 2)]);
839
840        assert_ne!(symbols.context_hash(), before);
841    }
842
843    #[test]
844    fn child_context_hash_observes_parent_declaration_update() {
845        let parent = SymbolTablePtr::new();
846        let mut declaration =
847            DeclarationPtr::new_find(Name::user("x"), Domain::int(vec![Range::Bounded(1, 3)]));
848        parent.write().insert(declaration.clone()).unwrap();
849        let child = SymbolTable::with_parent(parent);
850        let before = child.context_hash();
851
852        declaration.as_find_mut().unwrap().domain = Domain::int(vec![Range::Bounded(1, 2)]);
853
854        assert_ne!(child.context_hash(), before);
855    }
856
857    #[test]
858    fn symbol_table_pointer_equality_and_hash_are_identity_based() {
859        let left = SymbolTablePtr::new();
860        let right = SymbolTablePtr::new();
861        assert_ne!(left, right);
862
863        let mut left_hasher = DefaultHasher::new();
864        left.hash(&mut left_hasher);
865        let mut clone_hasher = DefaultHasher::new();
866        left.clone().hash(&mut clone_hasher);
867        assert_eq!(left_hasher.finish(), clone_hasher.finish());
868    }
869}