conjure_cp_core/ast/
symbol_table.rs

1//! The symbol table.
2//!
3//! See the item documentation for [`SymbolTable`] for more details.
4
5use crate::bug;
6use crate::representation::{Representation, get_repr_rule};
7
8use super::comprehension::Comprehension;
9use super::serde::RcRefCellAsId;
10use std::cell::RefCell;
11use std::collections::BTreeSet;
12use std::collections::btree_map::Entry;
13use std::collections::{BTreeMap, VecDeque};
14use std::hash::{Hash, Hasher};
15use std::rc::Rc;
16use std::sync::atomic::{AtomicU32, Ordering};
17
18use super::declaration::{DeclarationPtr, serde::DeclarationPtrFull};
19use super::serde::{DefaultWithId, HasId, ObjId};
20use itertools::{Itertools as _, izip};
21use serde::{Deserialize, Serialize};
22use serde_with::serde_as;
23use uniplate::Tree;
24use uniplate::{Biplate, Uniplate};
25
26use super::name::Name;
27use super::{DomainPtr, Expression, GroundDomain, Moo, ReturnType, SubModel, Typeable};
28use derivative::Derivative;
29
30// Count symbol tables per thread / model.
31//
32// We run tests in parallel and having the id's be per thread keeps them more deterministic in the
33// JSON output. If this were not thread local, ids would be given to symbol tables differently in
34// each test run (depending on how the threads were scheduled). These id changes would result in
35// all the generated tests "changing" each time `ACCEPT=true cargo test` is ran.
36//
37// SAFETY: Symbol tables use Rc<RefCell<<>>, so a model is not thread-safe anyways.
38thread_local! {
39static ID_COUNTER: AtomicU32 = const { AtomicU32::new(0) };
40}
41
42/// The global symbol table, mapping names to their definitions.
43///
44/// Names in the symbol table are unique, including between different types of object stored in the
45/// symbol table. For example, you cannot have a letting and decision variable with the same name.
46///
47/// # Symbol Kinds
48///
49/// The symbol table tracks the following types of symbol:
50///
51/// ## Decision Variables
52///
53/// ```text
54/// find NAME: DOMAIN
55/// ```
56///
57/// See [`DecisionVariable`](super::DecisionVariable).
58///
59/// ## Lettings
60///
61/// Lettings define constants, of which there are two types:
62///
63///   + **Constant values**: `letting val be A`, where A is an [`Expression`].
64///
65///     A can be any integer, boolean, or matrix expression.
66///     A can include references to other lettings, model parameters, and, unlike Savile Row,
67///     decision variables.
68///
69///   + **Constant domains**: `letting Domain be domain D`, where D is a [`Domain`].
70///
71///     D can include references to other lettings and model parameters, and, unlike Savile Row,
72///     decision variables.
73///
74/// Unless otherwise stated, these follow the semantics specified in section 2.2.2 of the Savile
75/// Row manual (version 1.9.1 at time of writing).
76#[derive(Derivative)]
77#[derivative(PartialEq)]
78#[derive(Debug, Eq)]
79#[serde_as]
80#[derive(Serialize, Deserialize)]
81pub struct SymbolTable {
82    #[serde_as(as = "Vec<(_,DeclarationPtrFull)>")]
83    table: BTreeMap<Name, DeclarationPtr>,
84
85    /// A unique id for this symbol table, for serialisation and debugging.
86    #[derivative(PartialEq = "ignore")] // eq by value not id.
87    id: ObjId,
88
89    #[serde_as(as = "Option<RcRefCellAsId>")]
90    parent: Option<Rc<RefCell<SymbolTable>>>,
91
92    next_machine_name: RefCell<i32>,
93}
94
95impl Hash for SymbolTable {
96    fn hash<H: Hasher>(&self, state: &mut H) {
97        self.id.hash(state);
98    }
99}
100
101impl SymbolTable {
102    /// Creates an empty symbol table.
103    pub fn new() -> SymbolTable {
104        SymbolTable::new_inner(None)
105    }
106
107    /// Creates an empty symbol table with the given parent.
108    pub fn with_parent(parent: Rc<RefCell<SymbolTable>>) -> SymbolTable {
109        SymbolTable::new_inner(Some(parent))
110    }
111
112    fn new_inner(parent: Option<Rc<RefCell<SymbolTable>>>) -> SymbolTable {
113        let id = ID_COUNTER.with(|x| x.fetch_add(1, Ordering::Relaxed));
114        SymbolTable {
115            id,
116            table: BTreeMap::new(),
117            next_machine_name: RefCell::new(0),
118            parent,
119        }
120    }
121
122    /// Looks up the declaration with the given name in the current scope only.
123    ///
124    /// Returns `None` if there is no declaration with that name in the current scope.
125    pub fn lookup_local(&self, name: &Name) -> Option<DeclarationPtr> {
126        self.table.get(name).cloned()
127    }
128
129    /// Looks up the declaration with the given name, checking all enclosing scopes.
130    ///
131    /// Returns `None` if there is no declaration with that name in scope.
132    pub fn lookup(&self, name: &Name) -> Option<DeclarationPtr> {
133        self.lookup_local(name).or_else(|| {
134            self.parent
135                .as_ref()
136                .and_then(|parent| (*parent).borrow().lookup(name))
137        })
138    }
139
140    /// Inserts a declaration into the symbol table.
141    ///
142    /// Returns `None` if there is already a symbol with this name in the local scope.
143    pub fn insert(&mut self, declaration: DeclarationPtr) -> Option<()> {
144        let name = declaration.name().clone();
145        if let Entry::Vacant(e) = self.table.entry(name) {
146            e.insert(declaration);
147            Some(())
148        } else {
149            None
150        }
151    }
152
153    /// Updates or adds a declaration in the immediate local scope.
154    pub fn update_insert(&mut self, declaration: DeclarationPtr) {
155        let name = declaration.name().clone();
156        self.table.insert(name, declaration);
157    }
158
159    /// Looks up the return type for name if it has one and is in scope.
160    pub fn return_type(&self, name: &Name) -> Option<ReturnType> {
161        self.lookup(name).map(|x| x.return_type())
162    }
163
164    /// Looks up the return type for name if has one and is in the local scope.
165    pub fn return_type_local(&self, name: &Name) -> Option<ReturnType> {
166        self.lookup_local(name).map(|x| x.return_type())
167    }
168
169    /// Looks up the domain of name if it has one and is in scope.
170    ///
171    /// This method can return domain references: if a ground domain is always required, use
172    /// [`SymbolTable::resolve_domain`].
173    pub fn domain(&self, name: &Name) -> Option<DomainPtr> {
174        if let Name::WithRepresentation(name, _) = name {
175            self.lookup(name)?.domain()
176        } else {
177            self.lookup(name)?.domain()
178        }
179    }
180
181    /// Looks up the domain of name, resolving domain references to ground domains.
182    ///
183    /// See [`SymbolTable::domain`].
184    pub fn resolve_domain(&self, name: &Name) -> Option<Moo<GroundDomain>> {
185        self.domain(name)?.resolve()
186    }
187
188    /// Iterates over entries in the local symbol table only.
189    pub fn into_iter_local(self) -> LocalIntoIter {
190        LocalIntoIter {
191            inner: self.table.into_iter(),
192        }
193    }
194
195    /// Extends the symbol table with the given symbol table, updating the gensym counter if
196    /// necessary.
197    pub fn extend(&mut self, other: SymbolTable) {
198        if other.table.keys().count() > self.table.keys().count() {
199            let new_vars = other.table.keys().collect::<BTreeSet<_>>();
200            let old_vars = self.table.keys().collect::<BTreeSet<_>>();
201
202            for added_var in new_vars.difference(&old_vars) {
203                let mut next_var = self.next_machine_name.borrow_mut();
204                if let Name::Machine(m) = *added_var
205                    && *m >= *next_var
206                {
207                    *next_var = *m + 1;
208                }
209            }
210        }
211
212        self.table.extend(other.table);
213    }
214
215    /// Creates a new variable in this symbol table with a unique name, and returns its
216    /// declaration.
217    pub fn gensym(&mut self, domain: &DomainPtr) -> DeclarationPtr {
218        let num = *self.next_machine_name.borrow();
219        *(self.next_machine_name.borrow_mut()) += 1;
220        let decl = DeclarationPtr::new_var(Name::Machine(num), domain.clone());
221        self.insert(decl.clone());
222        decl
223    }
224
225    /// Gets the parent of this symbol table as a mutable reference.
226    ///
227    /// This function provides no sanity checks.
228    pub fn parent_mut_unchecked(&mut self) -> &mut Option<Rc<RefCell<SymbolTable>>> {
229        &mut self.parent
230    }
231
232    /// Gets the representation `representation` for `name`.
233    ///
234    /// # Returns
235    ///
236    /// + `None` if `name` does not exist, is not a decision variable, or does not have that representation.
237    pub fn get_representation(
238        &self,
239        name: &Name,
240        representation: &[&str],
241    ) -> Option<Vec<Box<dyn Representation>>> {
242        // TODO: move representation stuff to declaration / variable to avoid cloning? (we have to
243        // move inside of an rc here, so cannot return borrows)
244        //
245        // Also would prevent constant "does exist" "is var" checks.
246        //
247        // The reason it is not there now is because I'm getting serde issues...
248        //
249        // Also might run into issues putting get_or_add into declaration/variable, as that
250        // requires us to mutably borrow both the symbol table, and the variable inside the symbol
251        // table..
252
253        let decl = self.lookup(name)?;
254        let var = &decl.as_var()?;
255
256        var.representations
257            .iter()
258            .find(|x| &x.iter().map(|r| r.repr_name()).collect_vec()[..] == representation)
259            .cloned()
260    }
261
262    /// Gets all initialised representations for `name`.
263    ///
264    /// # Returns
265    ///
266    /// + `None` if `name` does not exist, or is not a decision variable.
267    pub fn representations_for(&self, name: &Name) -> Option<Vec<Vec<Box<dyn Representation>>>> {
268        let decl = self.lookup(name)?;
269        decl.as_var().map(|x| x.representations.clone())
270    }
271
272    /// Gets the representation `representation` for `name`, creating it if it does not exist.
273    ///
274    /// If the representation does not exist, this method initialises the representation in this
275    /// symbol table, adding the representation to `name`, and the declarations for the represented
276    /// variables to the symbol table.
277    ///
278    /// # Usage
279    ///
280    /// Representations for variable references should be selected and created by the
281    /// `select_representation` rule. Therefore, this method should not be used in other rules.
282    /// Consider using [`get_representation`](`SymbolTable::get_representation`) instead.
283    ///
284    /// # Returns
285    ///
286    /// + `None` if `name` does not exist, is not a decision variable, or cannot be given that
287    ///   representation.
288    pub fn get_or_add_representation(
289        &mut self,
290        name: &Name,
291        representation: &[&str],
292    ) -> Option<Vec<Box<dyn Representation>>> {
293        // Lookup the declaration reference
294        let mut decl = self.lookup(name)?;
295
296        if let Some(var) = decl.as_var()
297            && let Some(existing_reprs) = var
298                .representations
299                .iter()
300                .find(|x| &x.iter().map(|r| r.repr_name()).collect_vec()[..] == representation)
301                .cloned()
302        {
303            return Some(existing_reprs); // Found: return early
304        }
305        // Representation not found
306
307        // TODO: nested representations logic...
308        if representation.len() != 1 {
309            bug!("nested representations not implemented")
310        }
311        let repr_name_str = representation[0];
312        let repr_init_fn = get_repr_rule(repr_name_str)?;
313
314        let reprs = vec![repr_init_fn(name, self)?];
315
316        // Get mutable access to the variable part
317        let mut var = decl.as_var_mut()?;
318
319        for repr_instance in &reprs {
320            repr_instance
321                .declaration_down()
322                .ok()?
323                .into_iter()
324                .for_each(|x| self.update_insert(x));
325        }
326
327        var.representations.push(reprs.clone());
328
329        Some(reprs)
330    }
331}
332
333impl IntoIterator for SymbolTable {
334    type Item = (Name, DeclarationPtr);
335
336    type IntoIter = IntoIter;
337
338    /// Iterates over symbol table entries in scope.
339    fn into_iter(self) -> Self::IntoIter {
340        IntoIter {
341            inner: self.table.into_iter(),
342            parent: self.parent,
343        }
344    }
345}
346
347/// Iterator over symbol table entries in the current scope only.
348pub struct LocalIntoIter {
349    // iterator over the btreemap
350    inner: std::collections::btree_map::IntoIter<Name, DeclarationPtr>,
351}
352
353impl Iterator for LocalIntoIter {
354    type Item = (Name, DeclarationPtr);
355
356    fn next(&mut self) -> Option<Self::Item> {
357        self.inner.next()
358    }
359}
360
361/// Iterator over all symbol table entries in scope.
362pub struct IntoIter {
363    // iterator over the current scopes' btreemap
364    inner: std::collections::btree_map::IntoIter<Name, DeclarationPtr>,
365
366    // the parent scope
367    parent: Option<Rc<RefCell<SymbolTable>>>,
368}
369
370impl Iterator for IntoIter {
371    type Item = (Name, DeclarationPtr);
372
373    fn next(&mut self) -> Option<Self::Item> {
374        let mut val = self.inner.next();
375
376        // Go up the tree until we find a parent symbol table with declarations to iterate over.
377        //
378        // Note that the parent symbol table may be empty - this is why this is a loop!
379        while val.is_none() {
380            let parent = self.parent.clone()?;
381            let parent_ref = (*parent).borrow();
382            self.parent.clone_from(&parent_ref.parent);
383            self.inner = parent_ref.table.clone().into_iter();
384
385            val = self.inner.next();
386        }
387
388        val
389    }
390}
391
392impl HasId for SymbolTable {
393    fn id(&self) -> ObjId {
394        self.id
395    }
396}
397
398impl DefaultWithId for SymbolTable {
399    fn default_with_id(id: ObjId) -> Self {
400        Self {
401            table: BTreeMap::new(),
402            id,
403            parent: None,
404            next_machine_name: RefCell::new(0),
405        }
406    }
407}
408
409impl Clone for SymbolTable {
410    fn clone(&self) -> Self {
411        Self {
412            table: self.table.clone(),
413            id: ID_COUNTER.with(|x| x.fetch_add(1, Ordering::Relaxed)),
414            parent: self.parent.clone(),
415            next_machine_name: self.next_machine_name.clone(),
416        }
417    }
418}
419
420impl Default for SymbolTable {
421    fn default() -> Self {
422        Self::new_inner(None)
423    }
424}
425
426impl Uniplate for SymbolTable {
427    fn uniplate(&self) -> (Tree<Self>, Box<dyn Fn(Tree<Self>) -> Self>) {
428        // do not recurse up parents, that would be weird?
429        let self2 = self.clone();
430        (Tree::Zero, Box::new(move |_| self2.clone()))
431    }
432}
433
434impl Biplate<Expression> for SymbolTable {
435    fn biplate(&self) -> (Tree<Expression>, Box<dyn Fn(Tree<Expression>) -> Self>) {
436        let (child_trees, ctxs): (VecDeque<_>, Vec<_>) = self
437            .table
438            .values()
439            .map(Biplate::<Expression>::biplate)
440            .unzip();
441
442        let tree = Tree::Many(child_trees);
443
444        let self2 = self.clone();
445        let ctx = Box::new(move |tree| {
446            let Tree::Many(exprs) = tree else {
447                panic!("unexpected children structure");
448            };
449
450            let mut self3 = self2.clone();
451            let self3_iter = self3.table.iter_mut();
452            for (ctx, tree, (_, decl)) in izip!(&ctxs, exprs, self3_iter) {
453                // update declaration inside the pointer instead of creating a new one, so all
454                // things referencing this keep referencing this.
455                *decl = ctx(tree)
456            }
457
458            self3
459        });
460
461        (tree, ctx)
462    }
463}
464
465impl Biplate<Comprehension> for SymbolTable {
466    fn biplate(
467        &self,
468    ) -> (
469        Tree<Comprehension>,
470        Box<dyn Fn(Tree<Comprehension>) -> Self>,
471    ) {
472        let (expr_tree, expr_ctx) = <SymbolTable as Biplate<Expression>>::biplate(self);
473
474        let (exprs, recons_expr_tree) = expr_tree.list();
475
476        let (comprehension_tree, comprehension_ctx) =
477            <VecDeque<Expression> as Biplate<Comprehension>>::biplate(&exprs);
478
479        let ctx = Box::new(move |x| {
480            // 1. turn comprehension tree into a list of expressions
481            let exprs = comprehension_ctx(x);
482
483            // 2. turn list of expressions into an expression tree
484            let expr_tree = recons_expr_tree(exprs);
485
486            // 3. turn expression tree into a symbol table
487            expr_ctx(expr_tree)
488        });
489
490        (comprehension_tree, ctx)
491    }
492}
493
494impl Biplate<SubModel> for SymbolTable {
495    // walk into expressions
496    fn biplate(&self) -> (Tree<SubModel>, Box<dyn Fn(Tree<SubModel>) -> Self>) {
497        let (expr_tree, expr_ctx) = <SymbolTable as Biplate<Expression>>::biplate(self);
498
499        let (exprs, recons_expr_tree) = expr_tree.list();
500
501        let (submodel_tree, submodel_ctx) =
502            <VecDeque<Expression> as Biplate<SubModel>>::biplate(&exprs);
503
504        let ctx = Box::new(move |x| {
505            // 1. turn submodel tree into a list of expressions
506            let exprs = submodel_ctx(x);
507
508            // 2. turn list of expressions into an expression tree
509            let expr_tree = recons_expr_tree(exprs);
510
511            // 3. turn expression tree into a symbol table
512            expr_ctx(expr_tree)
513        });
514        (submodel_tree, ctx)
515    }
516}